Skip to content

Commit dc355de

Browse files
authored
Merge pull request #12019 from Turbo87/trustpub-tests
trustpub/tokens/exchange: Improve tests
2 parents 6d0da7f + bc129f4 commit dc355de

File tree

1 file changed

+91
-21
lines changed

1 file changed

+91
-21
lines changed

src/controllers/trustpub/tokens/exchange/github_tests.rs

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ fn default_claims() -> FullGitHubClaims {
6868
.build()
6969
}
7070

71+
// ============================================================================
72+
// Success cases and token generation tests
73+
// ============================================================================
74+
7175
#[tokio::test(flavor = "multi_thread")]
7276
async fn test_happy_path() -> anyhow::Result<()> {
7377
let client = prepare().await?;
@@ -129,6 +133,31 @@ async fn test_happy_path_with_ignored_environment() -> anyhow::Result<()> {
129133
Ok(())
130134
}
131135

136+
/// Check that the owner name, repository name, and environment are accepted in
137+
/// a case-insensitive manner.
138+
#[tokio::test(flavor = "multi_thread")]
139+
async fn test_case_insensitive() -> anyhow::Result<()> {
140+
let client = prepare_with_config(|c| c.environment = Some("Prod")).await?;
141+
142+
let claims = FullGitHubClaims::builder()
143+
.owner_id(OWNER_ID)
144+
.owner_name("RUST-lanG")
145+
.repository_name("foo-RS")
146+
.workflow_filename(WORKFLOW_FILENAME)
147+
.environment("PROD")
148+
.build();
149+
150+
let body = claims.as_exchange_body()?;
151+
let response = client.post::<()>(URL, body).await;
152+
assert_snapshot!(response.status(), @"200 OK");
153+
154+
Ok(())
155+
}
156+
157+
// ============================================================================
158+
// JWT decode and validation tests
159+
// ============================================================================
160+
132161
#[tokio::test(flavor = "multi_thread")]
133162
async fn test_broken_jwt() -> anyhow::Result<()> {
134163
let client = prepare().await?;
@@ -249,6 +278,10 @@ async fn test_invalid_audience() -> anyhow::Result<()> {
249278
Ok(())
250279
}
251280

281+
// ============================================================================
282+
// JTI replay prevention tests
283+
// ============================================================================
284+
252285
/// Test that OIDC tokens can only be exchanged once
253286
#[tokio::test(flavor = "multi_thread")]
254287
async fn test_token_reuse() -> anyhow::Result<()> {
@@ -268,6 +301,10 @@ async fn test_token_reuse() -> anyhow::Result<()> {
268301
Ok(())
269302
}
270303

304+
// ============================================================================
305+
// Repository parsing tests
306+
// ============================================================================
307+
271308
#[tokio::test(flavor = "multi_thread")]
272309
async fn test_invalid_repository() -> anyhow::Result<()> {
273310
let client = prepare().await?;
@@ -283,6 +320,10 @@ async fn test_invalid_repository() -> anyhow::Result<()> {
283320
Ok(())
284321
}
285322

323+
// ============================================================================
324+
// Workflow filename extraction tests
325+
// ============================================================================
326+
286327
#[tokio::test(flavor = "multi_thread")]
287328
async fn test_invalid_workflow() -> anyhow::Result<()> {
288329
let client = prepare().await?;
@@ -298,6 +339,10 @@ async fn test_invalid_workflow() -> anyhow::Result<()> {
298339
Ok(())
299340
}
300341

342+
// ============================================================================
343+
// Repository owner ID validation tests
344+
// ============================================================================
345+
301346
#[tokio::test(flavor = "multi_thread")]
302347
async fn test_invalid_owner_id() -> anyhow::Result<()> {
303348
let client = prepare().await?;
@@ -313,6 +358,10 @@ async fn test_invalid_owner_id() -> anyhow::Result<()> {
313358
Ok(())
314359
}
315360

361+
// ============================================================================
362+
// Config lookup tests
363+
// ============================================================================
364+
316365
#[tokio::test(flavor = "multi_thread")]
317366
async fn test_missing_config() -> anyhow::Result<()> {
318367
let (_app, client, _cookie) = TestApp::full()
@@ -328,50 +377,71 @@ async fn test_missing_config() -> anyhow::Result<()> {
328377
Ok(())
329378
}
330379

380+
// ============================================================================
381+
// Repository owner ID verification (resurrection protection) tests
382+
// ============================================================================
383+
331384
#[tokio::test(flavor = "multi_thread")]
332-
async fn test_missing_environment() -> anyhow::Result<()> {
333-
let client = prepare_with_config(|c| c.environment = Some("prod")).await?;
385+
async fn test_repository_owner_id_mismatch() -> anyhow::Result<()> {
386+
let client = prepare_with_config(|c| {
387+
c.repository_owner_id = 999; // Different from OWNER_ID (42)
388+
})
389+
.await?;
334390

335391
let body = default_claims().as_exchange_body()?;
336392
let response = client.post::<()>(URL, body).await;
337393
assert_snapshot!(response.status(), @"400 Bad Request");
338-
assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` requires an environment, but the JWT does not specify one. Expected environments: `prod`"}]}"#);
394+
assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` does not match the repository owner ID (42) in the JWT. Expected owner IDs: 999. Please recreate the Trusted Publishing config to update the repository owner ID."}]}"#);
339395

340396
Ok(())
341397
}
342398

399+
// ============================================================================
400+
// Workflow filename matching tests
401+
// ============================================================================
402+
343403
#[tokio::test(flavor = "multi_thread")]
344-
async fn test_wrong_environment() -> anyhow::Result<()> {
345-
let client = prepare_with_config(|c| c.environment = Some("prod")).await?;
404+
async fn test_workflow_filename_mismatch() -> anyhow::Result<()> {
405+
let client = prepare_with_config(|c| {
406+
c.workflow_filename = "different.yml";
407+
})
408+
.await?;
346409

347-
let mut claims = default_claims();
348-
claims.environment = Some("not-prod".into());
410+
let body = default_claims().as_exchange_body()?;
411+
let response = client.post::<()>(URL, body).await;
412+
assert_snapshot!(response.status(), @"400 Bad Request");
413+
assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` does not match the workflow filename `publish.yml` in the JWT. Expected workflow filenames: `different.yml`"}]}"#);
349414

350-
let body = claims.as_exchange_body()?;
415+
Ok(())
416+
}
417+
418+
// ============================================================================
419+
// Environment matching tests
420+
// ============================================================================
421+
422+
#[tokio::test(flavor = "multi_thread")]
423+
async fn test_missing_environment() -> anyhow::Result<()> {
424+
let client = prepare_with_config(|c| c.environment = Some("prod")).await?;
425+
426+
let body = default_claims().as_exchange_body()?;
351427
let response = client.post::<()>(URL, body).await;
352428
assert_snapshot!(response.status(), @"400 Bad Request");
353-
assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` does not match the environment `not-prod` in the JWT. Expected environments: `prod`"}]}"#);
429+
assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` requires an environment, but the JWT does not specify one. Expected environments: `prod`"}]}"#);
354430

355431
Ok(())
356432
}
357433

358-
/// Check that the owner name, repository name, and environment are accepted in
359-
/// a case-insensitive manner.
360434
#[tokio::test(flavor = "multi_thread")]
361-
async fn test_case_insensitive() -> anyhow::Result<()> {
362-
let client = prepare_with_config(|c| c.environment = Some("Prod")).await?;
435+
async fn test_wrong_environment() -> anyhow::Result<()> {
436+
let client = prepare_with_config(|c| c.environment = Some("prod")).await?;
363437

364-
let claims = FullGitHubClaims::builder()
365-
.owner_id(OWNER_ID)
366-
.owner_name("RUST-lanG")
367-
.repository_name("foo-RS")
368-
.workflow_filename(WORKFLOW_FILENAME)
369-
.environment("PROD")
370-
.build();
438+
let mut claims = default_claims();
439+
claims.environment = Some("not-prod".into());
371440

372441
let body = claims.as_exchange_body()?;
373442
let response = client.post::<()>(URL, body).await;
374-
assert_snapshot!(response.status(), @"200 OK");
443+
assert_snapshot!(response.status(), @"400 Bad Request");
444+
assert_snapshot!(response.json(), @r#"{"errors":[{"detail":"The Trusted Publishing config for repository `rust-lang/foo-rs` does not match the environment `not-prod` in the JWT. Expected environments: `prod`"}]}"#);
375445

376446
Ok(())
377447
}

0 commit comments

Comments
 (0)