From e31813aedc2eb8d4fc1d779647fa3a31d6ad653f Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Mon, 10 Nov 2025 12:34:21 +0100 Subject: [PATCH] trustpub/tests: Nest GitHub tests under Trusted Publishing module Move GitHub-specific trusted publishing tests into a dedicated submodule to prepare for GitLab test addition. Test structure is now: - Trusted Publishing (parent) - GitHub (tests for GitHub configs) - GitLab (future home for GitLab config tests) --- e2e/routes/crate/settings.spec.ts | 158 ++++---- .../settings/new-trusted-publisher.spec.ts | 350 +++++++++-------- tests/routes/crate/settings-test.js | 152 ++++---- .../settings/new-trusted-publisher-test.js | 358 +++++++++--------- 4 files changed, 525 insertions(+), 493 deletions(-) diff --git a/e2e/routes/crate/settings.spec.ts b/e2e/routes/crate/settings.spec.ts index 2eba1136335..c3d3b6b7c0e 100644 --- a/e2e/routes/crate/settings.spec.ts +++ b/e2e/routes/crate/settings.spec.ts @@ -59,87 +59,89 @@ test.describe('Route | crate.settings', { tag: '@routes' }, () => { }); test.describe('Trusted Publishing', () => { - test('happy path', async ({ msw, page, percy }) => { - const { crate } = await prepare(msw); - - // Create two GitHub configs for the crate - msw.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'rust-lang', - repository_name: 'crates.io', - workflow_filename: 'ci.yml', + test.describe('GitHub', () => { + test('happy path', async ({ msw, page, percy }) => { + const { crate } = await prepare(msw); + + // Create two GitHub configs for the crate + msw.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'rust-lang', + repository_name: 'crates.io', + workflow_filename: 'ci.yml', + }); + + msw.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'johndoe', + repository_name: 'crates.io', + workflow_filename: 'release.yml', + environment: 'release', + }); + + await page.goto('/crates/foo/settings'); + await expect(page).toHaveURL('/crates/foo/settings'); + + await expect(page.locator('[data-test-trusted-publishing]')).toBeVisible(); + await expect(page.locator('[data-test-add-trusted-publisher-button]')).toBeVisible(); + await expect(page.locator('[data-test-github-config]')).toHaveCount(2); + await expect(page.locator('[data-test-github-config="1"] td:nth-child(1)')).toHaveText('GitHub'); + let details = page.locator('[data-test-github-config="1"] td:nth-child(2)'); + await expect(details).toContainText('Repository: rust-lang/crates.io'); + await expect(details).toContainText('Workflow: ci.yml'); + await expect(details).not.toContainText('Environment'); + await expect(page.locator('[data-test-github-config="1"] [data-test-remove-config-button]')).toBeVisible(); + await expect(page.locator('[data-test-github-config="2"] td:nth-child(1)')).toHaveText('GitHub'); + details = page.locator('[data-test-github-config="2"] td:nth-child(2)'); + await expect(details).toContainText('Repository: johndoe/crates.io'); + await expect(details).toContainText('Workflow: release.yml'); + await expect(details).toContainText('Environment: release'); + await expect(page.locator('[data-test-github-config="2"] [data-test-remove-config-button]')).toBeVisible(); + await expect(page.locator('[data-test-no-config]')).not.toBeVisible(); + + await percy.snapshot(); + + // Click the remove button + await page.click('[data-test-github-config="2"] [data-test-remove-config-button]'); + + // Check that the config is no longer displayed + await expect(page.locator('[data-test-github-config]')).toHaveCount(1); + details = page.locator('[data-test-github-config="1"] td:nth-child(2)'); + await expect(details).toContainText('Repository: rust-lang/crates.io'); + await expect(page.locator('[data-test-notification-message]')).toHaveText( + 'Trusted Publishing configuration removed successfully', + ); }); - msw.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'johndoe', - repository_name: 'crates.io', - workflow_filename: 'release.yml', - environment: 'release', + test('deletion failure', async ({ msw, page, percy }) => { + let { crate } = await prepare(msw); + + // Create a GitHub config for the crate + let config = msw.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'rust-lang', + repository_name: 'crates.io', + workflow_filename: 'ci.yml', + environment: 'release', + }); + + // Mock the server to return an error when trying to delete the config + await msw.worker.use( + http.delete(`/api/v1/trusted_publishing/github_configs/${config.id}`, () => { + return HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 }); + }), + ); + + await page.goto(`/crates/${crate.name}/settings`); + await expect(page).toHaveURL(`/crates/${crate.name}/settings`); + await expect(page.locator('[data-test-github-config]')).toHaveCount(1); + + await page.click('[data-test-remove-config-button]'); + await expect(page.locator('[data-test-github-config]')).toHaveCount(1); + await expect(page.locator('[data-test-notification-message]')).toHaveText( + 'Failed to remove Trusted Publishing configuration: Server error', + ); }); - - await page.goto('/crates/foo/settings'); - await expect(page).toHaveURL('/crates/foo/settings'); - - await expect(page.locator('[data-test-trusted-publishing]')).toBeVisible(); - await expect(page.locator('[data-test-add-trusted-publisher-button]')).toBeVisible(); - await expect(page.locator('[data-test-github-config]')).toHaveCount(2); - await expect(page.locator('[data-test-github-config="1"] td:nth-child(1)')).toHaveText('GitHub'); - let details = page.locator('[data-test-github-config="1"] td:nth-child(2)'); - await expect(details).toContainText('Repository: rust-lang/crates.io'); - await expect(details).toContainText('Workflow: ci.yml'); - await expect(details).not.toContainText('Environment'); - await expect(page.locator('[data-test-github-config="1"] [data-test-remove-config-button]')).toBeVisible(); - await expect(page.locator('[data-test-github-config="2"] td:nth-child(1)')).toHaveText('GitHub'); - details = page.locator('[data-test-github-config="2"] td:nth-child(2)'); - await expect(details).toContainText('Repository: johndoe/crates.io'); - await expect(details).toContainText('Workflow: release.yml'); - await expect(details).toContainText('Environment: release'); - await expect(page.locator('[data-test-github-config="2"] [data-test-remove-config-button]')).toBeVisible(); - await expect(page.locator('[data-test-no-config]')).not.toBeVisible(); - - await percy.snapshot(); - - // Click the remove button - await page.click('[data-test-github-config="2"] [data-test-remove-config-button]'); - - // Check that the config is no longer displayed - await expect(page.locator('[data-test-github-config]')).toHaveCount(1); - details = page.locator('[data-test-github-config="1"] td:nth-child(2)'); - await expect(details).toContainText('Repository: rust-lang/crates.io'); - await expect(page.locator('[data-test-notification-message]')).toHaveText( - 'Trusted Publishing configuration removed successfully', - ); - }); - - test('deletion failure', async ({ msw, page, percy }) => { - let { crate } = await prepare(msw); - - // Create a GitHub config for the crate - let config = msw.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'rust-lang', - repository_name: 'crates.io', - workflow_filename: 'ci.yml', - environment: 'release', - }); - - // Mock the server to return an error when trying to delete the config - await msw.worker.use( - http.delete(`/api/v1/trusted_publishing/github_configs/${config.id}`, () => { - return HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 }); - }), - ); - - await page.goto(`/crates/${crate.name}/settings`); - await expect(page).toHaveURL(`/crates/${crate.name}/settings`); - await expect(page.locator('[data-test-github-config]')).toHaveCount(1); - - await page.click('[data-test-remove-config-button]'); - await expect(page.locator('[data-test-github-config]')).toHaveCount(1); - await expect(page.locator('[data-test-notification-message]')).toHaveText( - 'Failed to remove Trusted Publishing configuration: Server error', - ); }); }); }); diff --git a/e2e/routes/crate/settings/new-trusted-publisher.spec.ts b/e2e/routes/crate/settings/new-trusted-publisher.spec.ts index 78c90e30dc7..070769923e0 100644 --- a/e2e/routes/crate/settings/new-trusted-publisher.spec.ts +++ b/e2e/routes/crate/settings/new-trusted-publisher.spec.ts @@ -37,139 +37,6 @@ test.describe('Route | crate.settings.new-trusted-publisher', { tag: '@routes' } await expect(page.locator('[data-test-go-back]')).toBeVisible(); }); - test('happy path', async ({ msw, page, percy }) => { - let { crate } = await prepare(msw); - - msw.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'johndoe', - repository_name: 'crates.io', - workflow_filename: 'release.yml', - }); - - await page.goto(`/crates/${crate.name}/settings`); - await page.click('[data-test-add-trusted-publisher-button]'); - await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); - - await percy.snapshot(); - - // Check that the form is displayed correctly - await expect(page.locator('[data-test-publisher]')).toBeVisible(); - await expect(page.locator('[data-test-repository-owner]')).toBeVisible(); - await expect(page.locator('[data-test-repository-name]')).toBeVisible(); - await expect(page.locator('[data-test-workflow-filename]')).toBeVisible(); - await expect(page.locator('[data-test-environment]')).toBeVisible(); - await expect(page.locator('[data-test-add]')).toBeVisible(); - await expect(page.locator('[data-test-cancel]')).toBeVisible(); - - // Fill in the form - await page.fill('[data-test-repository-owner]', 'rust-lang'); - await page.fill('[data-test-repository-name]', 'crates.io'); - await page.fill('[data-test-workflow-filename]', 'ci.yml'); - await page.fill('[data-test-environment]', 'release'); - - // Submit the form - await page.click('[data-test-add]'); - - // Check that we're redirected back to the crate settings page - await expect(page).toHaveURL(`/crates/${crate.name}/settings`); - - // Check that the config was created - let config = msw.db.trustpubGithubConfig.findFirst({ - where: { - repository_owner: { equals: 'rust-lang' }, - repository_name: { equals: 'crates.io' }, - workflow_filename: { equals: 'ci.yml' }, - environment: { equals: 'release' }, - }, - }); - expect(config, 'Config was created').toBeDefined(); - - // Check that the success notification is displayed - await expect(page.locator('[data-test-notification-message]')).toHaveText( - 'Trusted Publishing configuration added successfully', - ); - - // Check that the config is displayed on the crate settings page - await expect(page.locator('[data-test-github-config]')).toHaveCount(2); - await expect(page.locator('[data-test-github-config="2"] td:nth-child(1)')).toHaveText('GitHub'); - let details = page.locator('[data-test-github-config="2"] td:nth-child(2)'); - await expect(details).toContainText('Repository: rust-lang/crates.io'); - await expect(details).toContainText('Workflow: ci.yml'); - await expect(details).toContainText('Environment: release'); - }); - - test('validation errors', async ({ msw, page }) => { - let { crate } = await prepare(msw); - - await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); - await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); - - // Submit the form without filling in required fields - await page.click('[data-test-add]'); - - // Check that validation errors are displayed - await expect(page.locator('[data-test-repository-owner-group] [data-test-error]')).toBeVisible(); - await expect(page.locator('[data-test-repository-name-group] [data-test-error]')).toBeVisible(); - await expect(page.locator('[data-test-workflow-filename-group] [data-test-error]')).toBeVisible(); - - // Fill in the required fields - await page.fill('[data-test-repository-owner]', 'rust-lang'); - await page.fill('[data-test-repository-name]', 'crates.io'); - await page.fill('[data-test-workflow-filename]', 'ci.yml'); - - // Submit the form - await page.click('[data-test-add]'); - - // Check that we're redirected back to the crate settings page - await expect(page).toHaveURL(`/crates/${crate.name}/settings`); - }); - - test('loading and error state', async ({ msw, page }) => { - let { crate } = await prepare(msw); - - // Mock the server to return an error - let deferred = defer(); - msw.worker.use(http.post('/api/v1/trusted_publishing/github_configs', () => deferred.promise)); - - await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); - await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); - - // Fill in the form - await page.fill('[data-test-repository-owner]', 'rust-lang'); - await page.fill('[data-test-repository-name]', 'crates.io'); - await page.fill('[data-test-workflow-filename]', 'ci.yml'); - - // Submit the form - await page.click('[data-test-add]'); - await expect(page.locator('[data-test-add] [data-test-spinner]')).toBeVisible(); - await expect(page.locator('[data-test-publisher]')).toBeDisabled(); - await expect(page.locator('[data-test-repository-owner]')).toBeDisabled(); - await expect(page.locator('[data-test-repository-name]')).toBeDisabled(); - await expect(page.locator('[data-test-workflow-filename]')).toBeDisabled(); - await expect(page.locator('[data-test-environment]')).toBeDisabled(); - await expect(page.locator('[data-test-add]')).toBeDisabled(); - - // Resolve the deferred with an error - deferred.resolve(HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 })); - - // Check that the error notification is displayed - await expect(page.locator('[data-test-notification-message]')).toHaveText( - 'An error has occurred while adding the Trusted Publishing configuration: Server error', - ); - - await expect(page.locator('[data-test-publisher]')).toBeEnabled(); - await expect(page.locator('[data-test-repository-owner]')).toBeEnabled(); - await expect(page.locator('[data-test-repository-name]')).toBeEnabled(); - await expect(page.locator('[data-test-workflow-filename]')).toBeEnabled(); - await expect(page.locator('[data-test-environment]')).toBeEnabled(); - await expect(page.locator('[data-test-add]')).toBeEnabled(); - - await page.click('[data-test-cancel]'); - await expect(page).toHaveURL(`/crates/${crate.name}/settings`); - await expect(page.locator('[data-test-github-config]')).toHaveCount(0); - }); - test('cancel button', async ({ msw, page }) => { let { crate } = await prepare(msw); @@ -188,48 +55,55 @@ test.describe('Route | crate.settings.new-trusted-publisher', { tag: '@routes' } { name: 'simple https', url: 'https://github.com/rust-lang/crates.io', + publisher: 'GitHub', owner: 'rust-lang', repo: 'crates.io', }, { name: 'with .git suffix', url: 'https://github.com/rust-lang/crates.io.git', + publisher: 'GitHub', owner: 'rust-lang', repo: 'crates.io', }, { name: 'with extra path segments', url: 'https://github.com/Byron/google-apis-rs/tree/main/gen/privateca1', + publisher: 'GitHub', owner: 'Byron', repo: 'google-apis-rs', }, { name: 'non-github url', url: 'https://gitlab.com/rust-lang/crates.io', + publisher: 'GitHub', owner: '', repo: '', }, { name: 'not a url', url: 'not a url', + publisher: 'GitHub', owner: '', repo: '', }, { name: 'empty string', url: '', + publisher: 'GitHub', owner: '', repo: '', }, { name: 'null', url: null, + publisher: 'GitHub', owner: '', repo: '', }, ]; - for (const { name, url, owner, repo } of testCases) { + for (const { name, url, publisher, owner, repo } of testCases) { test(name, async ({ msw, page }) => { let { crate } = await prepare(msw); @@ -240,82 +114,222 @@ test.describe('Route | crate.settings.new-trusted-publisher', { tag: '@routes' } await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); + await expect(page.locator('[data-test-publisher]')).toHaveValue(publisher); await expect(page.locator('[data-test-repository-owner]')).toHaveValue(owner); await expect(page.locator('[data-test-repository-name]')).toHaveValue(repo); }); } }); - test.describe('workflow verification', () => { - test('success case (200 OK)', async ({ msw, page }) => { + test.describe('GitHub', () => { + test('happy path', async ({ msw, page, percy }) => { let { crate } = await prepare(msw); - await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); + msw.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'johndoe', + repository_name: 'crates.io', + workflow_filename: 'release.yml', + }); + + await page.goto(`/crates/${crate.name}/settings`); + await page.click('[data-test-add-trusted-publisher-button]'); await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); - await msw.worker.use( - http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { - return new HttpResponse(null, { status: 200 }); - }), - ); + await percy.snapshot(); - await expect(page.locator('[data-test-workflow-verification="initial"]')).toHaveText( - 'The workflow filename will be verified once all necessary fields are filled.', - ); + // Check that the form is displayed correctly + await expect(page.locator('[data-test-publisher]')).toBeVisible(); + await expect(page.locator('[data-test-repository-owner]')).toBeVisible(); + await expect(page.locator('[data-test-repository-name]')).toBeVisible(); + await expect(page.locator('[data-test-workflow-filename]')).toBeVisible(); + await expect(page.locator('[data-test-environment]')).toBeVisible(); + await expect(page.locator('[data-test-add]')).toBeVisible(); + await expect(page.locator('[data-test-cancel]')).toBeVisible(); + // Fill in the form await page.fill('[data-test-repository-owner]', 'rust-lang'); await page.fill('[data-test-repository-name]', 'crates.io'); await page.fill('[data-test-workflow-filename]', 'ci.yml'); + await page.fill('[data-test-environment]', 'release'); + + // Submit the form + await page.click('[data-test-add]'); - await expect(page.locator('[data-test-workflow-verification="success"]')).toHaveText( - '✓ Workflow file found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', + // Check that we're redirected back to the crate settings page + await expect(page).toHaveURL(`/crates/${crate.name}/settings`); + + // Check that the config was created + let config = msw.db.trustpubGithubConfig.findFirst({ + where: { + repository_owner: { equals: 'rust-lang' }, + repository_name: { equals: 'crates.io' }, + workflow_filename: { equals: 'ci.yml' }, + environment: { equals: 'release' }, + }, + }); + expect(config, 'Config was created').toBeDefined(); + + // Check that the success notification is displayed + await expect(page.locator('[data-test-notification-message]')).toHaveText( + 'Trusted Publishing configuration added successfully', ); + + // Check that the config is displayed on the crate settings page + await expect(page.locator('[data-test-github-config]')).toHaveCount(2); + await expect(page.locator('[data-test-github-config="2"] td:nth-child(1)')).toHaveText('GitHub'); + let details = page.locator('[data-test-github-config="2"] td:nth-child(2)'); + await expect(details).toContainText('Repository: rust-lang/crates.io'); + await expect(details).toContainText('Workflow: ci.yml'); + await expect(details).toContainText('Environment: release'); }); - test('not found case (404)', async ({ msw, page }) => { + test('validation errors', async ({ msw, page }) => { let { crate } = await prepare(msw); await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); - await msw.worker.use( - http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml', () => { - return new HttpResponse(null, { status: 404 }); - }), - ); + // Submit the form without filling in required fields + await page.click('[data-test-add]'); + + // Check that validation errors are displayed + await expect(page.locator('[data-test-repository-owner-group] [data-test-error]')).toBeVisible(); + await expect(page.locator('[data-test-repository-name-group] [data-test-error]')).toBeVisible(); + await expect(page.locator('[data-test-workflow-filename-group] [data-test-error]')).toBeVisible(); + // Fill in the required fields await page.fill('[data-test-repository-owner]', 'rust-lang'); await page.fill('[data-test-repository-name]', 'crates.io'); - await page.fill('[data-test-workflow-filename]', 'missing.yml'); - - await expect(page.locator('[data-test-workflow-verification="not-found"]')).toHaveText( - '⚠ Workflow file not found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml', - ); + await page.fill('[data-test-workflow-filename]', 'ci.yml'); - // Verify form can still be submitted + // Submit the form await page.click('[data-test-add]'); + + // Check that we're redirected back to the crate settings page await expect(page).toHaveURL(`/crates/${crate.name}/settings`); }); - test('server error (5xx)', async ({ msw, page }) => { + test('loading and error state', async ({ msw, page }) => { let { crate } = await prepare(msw); + // Mock the server to return an error + let deferred = defer(); + msw.worker.use(http.post('/api/v1/trusted_publishing/github_configs', () => deferred.promise)); + await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); - await msw.worker.use( - http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { - return new HttpResponse(null, { status: 500 }); - }), - ); - + // Fill in the form await page.fill('[data-test-repository-owner]', 'rust-lang'); await page.fill('[data-test-repository-name]', 'crates.io'); await page.fill('[data-test-workflow-filename]', 'ci.yml'); - await expect(page.locator('[data-test-workflow-verification="error"]')).toHaveText( - '⚠ Could not verify workflow file at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml (network error)', + // Submit the form + await page.click('[data-test-add]'); + await expect(page.locator('[data-test-add] [data-test-spinner]')).toBeVisible(); + await expect(page.locator('[data-test-publisher]')).toBeDisabled(); + await expect(page.locator('[data-test-repository-owner]')).toBeDisabled(); + await expect(page.locator('[data-test-repository-name]')).toBeDisabled(); + await expect(page.locator('[data-test-workflow-filename]')).toBeDisabled(); + await expect(page.locator('[data-test-environment]')).toBeDisabled(); + await expect(page.locator('[data-test-add]')).toBeDisabled(); + + // Resolve the deferred with an error + deferred.resolve(HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 })); + + // Check that the error notification is displayed + await expect(page.locator('[data-test-notification-message]')).toHaveText( + 'An error has occurred while adding the Trusted Publishing configuration: Server error', ); + + await expect(page.locator('[data-test-publisher]')).toBeEnabled(); + await expect(page.locator('[data-test-repository-owner]')).toBeEnabled(); + await expect(page.locator('[data-test-repository-name]')).toBeEnabled(); + await expect(page.locator('[data-test-workflow-filename]')).toBeEnabled(); + await expect(page.locator('[data-test-environment]')).toBeEnabled(); + await expect(page.locator('[data-test-add]')).toBeEnabled(); + + await page.click('[data-test-cancel]'); + await expect(page).toHaveURL(`/crates/${crate.name}/settings`); + await expect(page.locator('[data-test-github-config]')).toHaveCount(0); + }); + + test.describe('workflow verification', () => { + test('success case (200 OK)', async ({ msw, page }) => { + let { crate } = await prepare(msw); + + await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); + await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); + + await msw.worker.use( + http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { + return new HttpResponse(null, { status: 200 }); + }), + ); + + await expect(page.locator('[data-test-workflow-verification="initial"]')).toHaveText( + 'The workflow filename will be verified once all necessary fields are filled.', + ); + + await page.fill('[data-test-repository-owner]', 'rust-lang'); + await page.fill('[data-test-repository-name]', 'crates.io'); + await page.fill('[data-test-workflow-filename]', 'ci.yml'); + + await expect(page.locator('[data-test-workflow-verification="success"]')).toHaveText( + '✓ Workflow file found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', + ); + }); + + test('not found case (404)', async ({ msw, page }) => { + let { crate } = await prepare(msw); + + await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); + await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); + + await msw.worker.use( + http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml', () => { + return new HttpResponse(null, { status: 404 }); + }), + ); + + await page.fill('[data-test-repository-owner]', 'rust-lang'); + await page.fill('[data-test-repository-name]', 'crates.io'); + await page.fill('[data-test-workflow-filename]', 'missing.yml'); + + await expect(page.locator('[data-test-workflow-verification="not-found"]')).toHaveText( + '⚠ Workflow file not found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml', + ); + + // Verify form can still be submitted + await page.click('[data-test-add]'); + await expect(page).toHaveURL(`/crates/${crate.name}/settings`); + }); + + test('server error (5xx)', async ({ msw, page }) => { + let { crate } = await prepare(msw); + + await page.goto(`/crates/${crate.name}/settings/new-trusted-publisher`); + await expect(page).toHaveURL(`/crates/${crate.name}/settings/new-trusted-publisher`); + + await msw.worker.use( + http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { + return new HttpResponse(null, { status: 500 }); + }), + ); + + await page.fill('[data-test-repository-owner]', 'rust-lang'); + await page.fill('[data-test-repository-name]', 'crates.io'); + await page.fill('[data-test-workflow-filename]', 'ci.yml'); + + await expect(page.locator('[data-test-workflow-verification="error"]')).toHaveText( + '⚠ Could not verify workflow file at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml (network error)', + ); + }); }); }); + + test.describe('GitLab', () => { + // Placeholder for GitLab tests when they are implemented + }); }); diff --git a/tests/routes/crate/settings-test.js b/tests/routes/crate/settings-test.js index c05da7d94ff..50ce9ff5458 100644 --- a/tests/routes/crate/settings-test.js +++ b/tests/routes/crate/settings-test.js @@ -63,84 +63,86 @@ module('Route | crate.settings', hooks => { }); module('Trusted Publishing', function () { - test('happy path', async function (assert) { - const { crate, user } = prepare(this); - this.authenticateAs(user); - - // Create two GitHub configs for the crate - this.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'rust-lang', - repository_name: 'crates.io', - workflow_filename: 'ci.yml', + module('GitHub', function () { + test('happy path', async function (assert) { + const { crate, user } = prepare(this); + this.authenticateAs(user); + + // Create two GitHub configs for the crate + this.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'rust-lang', + repository_name: 'crates.io', + workflow_filename: 'ci.yml', + }); + + this.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'johndoe', + repository_name: 'crates.io', + workflow_filename: 'release.yml', + environment: 'release', + }); + + await visit(`/crates/${crate.name}/settings`); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); + + await percySnapshot(assert); + + // Check that the GitHub config is displayed + assert.dom('[data-test-trusted-publishing]').exists(); + assert.dom('[data-test-github-config]').exists({ count: 2 }); + assert.dom('[data-test-github-config="1"] td:nth-child(1)').hasText('GitHub'); + assert.dom('[data-test-github-config="1"] td:nth-child(2)').includesText('Repository: rust-lang/crates.io'); + assert.dom('[data-test-github-config="1"] td:nth-child(2)').includesText('Workflow: ci.yml'); + assert.dom('[data-test-github-config="1"] td:nth-child(2)').doesNotIncludeText('Environment'); + assert.dom('[data-test-github-config="1"] [data-test-remove-config-button]').exists(); + assert.dom('[data-test-github-config="2"] td:nth-child(1)').hasText('GitHub'); + assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Repository: johndoe/crates.io'); + assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Workflow: release.yml'); + assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Environment: release'); + assert.dom('[data-test-github-config="2"] [data-test-remove-config-button]').exists(); + assert.dom('[data-test-no-config]').doesNotExist(); + + // Click the remove button + await click('[data-test-github-config="2"] [data-test-remove-config-button]'); + + // Check that the config is no longer displayed + assert.dom('[data-test-github-config]').exists({ count: 1 }); + assert.dom('[data-test-github-config="1"] td:nth-child(2)').includesText('Repository: rust-lang/crates.io'); + assert.dom('[data-test-notification-message]').hasText('Trusted Publishing configuration removed successfully'); }); - this.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'johndoe', - repository_name: 'crates.io', - workflow_filename: 'release.yml', - environment: 'release', + test('deletion failure', async function (assert) { + let { crate, user } = prepare(this); + this.authenticateAs(user); + + // Create a GitHub config for the crate + let config = this.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'rust-lang', + repository_name: 'crates.io', + workflow_filename: 'ci.yml', + environment: 'release', + }); + + // Mock the server to return an error when trying to delete the config + this.worker.use( + http.delete(`/api/v1/trusted_publishing/github_configs/${config.id}`, () => { + return HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 }); + }), + ); + + await visit(`/crates/${crate.name}/settings`); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); + assert.dom('[data-test-github-config]').exists({ count: 1 }); + + await click('[data-test-remove-config-button]'); + assert.dom('[data-test-github-config]').exists({ count: 1 }); + assert + .dom('[data-test-notification-message]') + .hasText('Failed to remove Trusted Publishing configuration: Server error'); }); - - await visit(`/crates/${crate.name}/settings`); - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); - - await percySnapshot(assert); - - // Check that the GitHub config is displayed - assert.dom('[data-test-trusted-publishing]').exists(); - assert.dom('[data-test-github-config]').exists({ count: 2 }); - assert.dom('[data-test-github-config="1"] td:nth-child(1)').hasText('GitHub'); - assert.dom('[data-test-github-config="1"] td:nth-child(2)').includesText('Repository: rust-lang/crates.io'); - assert.dom('[data-test-github-config="1"] td:nth-child(2)').includesText('Workflow: ci.yml'); - assert.dom('[data-test-github-config="1"] td:nth-child(2)').doesNotIncludeText('Environment'); - assert.dom('[data-test-github-config="1"] [data-test-remove-config-button]').exists(); - assert.dom('[data-test-github-config="2"] td:nth-child(1)').hasText('GitHub'); - assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Repository: johndoe/crates.io'); - assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Workflow: release.yml'); - assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Environment: release'); - assert.dom('[data-test-github-config="2"] [data-test-remove-config-button]').exists(); - assert.dom('[data-test-no-config]').doesNotExist(); - - // Click the remove button - await click('[data-test-github-config="2"] [data-test-remove-config-button]'); - - // Check that the config is no longer displayed - assert.dom('[data-test-github-config]').exists({ count: 1 }); - assert.dom('[data-test-github-config="1"] td:nth-child(2)').includesText('Repository: rust-lang/crates.io'); - assert.dom('[data-test-notification-message]').hasText('Trusted Publishing configuration removed successfully'); - }); - - test('deletion failure', async function (assert) { - let { crate, user } = prepare(this); - this.authenticateAs(user); - - // Create a GitHub config for the crate - let config = this.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'rust-lang', - repository_name: 'crates.io', - workflow_filename: 'ci.yml', - environment: 'release', - }); - - // Mock the server to return an error when trying to delete the config - this.worker.use( - http.delete(`/api/v1/trusted_publishing/github_configs/${config.id}`, () => { - return HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 }); - }), - ); - - await visit(`/crates/${crate.name}/settings`); - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); - assert.dom('[data-test-github-config]').exists({ count: 1 }); - - await click('[data-test-remove-config-button]'); - assert.dom('[data-test-github-config]').exists({ count: 1 }); - assert - .dom('[data-test-notification-message]') - .hasText('Failed to remove Trusted Publishing configuration: Server error'); }); }); }); diff --git a/tests/routes/crate/settings/new-trusted-publisher-test.js b/tests/routes/crate/settings/new-trusted-publisher-test.js index 7c7adbfe8f2..e8fdfca94d7 100644 --- a/tests/routes/crate/settings/new-trusted-publisher-test.js +++ b/tests/routes/crate/settings/new-trusted-publisher-test.js @@ -47,137 +47,6 @@ module('Route | crate.settings.new-trusted-publisher', hooks => { assert.dom('[data-test-go-back]').exists(); }); - test('happy path', async function (assert) { - let { crate } = prepare(this); - - this.db.trustpubGithubConfig.create({ - crate, - repository_owner: 'johndoe', - repository_name: 'crates.io', - workflow_filename: 'release.yml', - }); - - await visit(`/crates/${crate.name}/settings`); - await click('[data-test-add-trusted-publisher-button]'); - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); - - await percySnapshot(assert); - - // Check that the form is displayed correctly - assert.dom('[data-test-publisher]').exists(); - assert.dom('[data-test-repository-owner]').exists(); - assert.dom('[data-test-repository-name]').exists(); - assert.dom('[data-test-workflow-filename]').exists(); - assert.dom('[data-test-environment]').exists(); - assert.dom('[data-test-add]').exists(); - assert.dom('[data-test-cancel]').exists(); - - // Fill in the form - await fillIn('[data-test-repository-owner]', 'rust-lang'); - await fillIn('[data-test-repository-name]', 'crates.io'); - await fillIn('[data-test-workflow-filename]', 'ci.yml'); - await fillIn('[data-test-environment]', 'release'); - - // Submit the form - await click('[data-test-add]'); - - // Check that we're redirected back to the crate settings page - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); - - // Check that the config was created - let config = this.db.trustpubGithubConfig.findFirst({ - where: { - repository_owner: { equals: 'rust-lang' }, - repository_name: { equals: 'crates.io' }, - workflow_filename: { equals: 'ci.yml' }, - environment: { equals: 'release' }, - }, - }); - assert.ok(config, 'Config was created'); - - // Check that the success notification is displayed - assert.dom('[data-test-notification-message]').hasText('Trusted Publishing configuration added successfully'); - - // Check that the config is displayed on the crate settings page - assert.dom('[data-test-github-config]').exists({ count: 2 }); - assert.dom('[data-test-github-config="2"] td:nth-child(1)').hasText('GitHub'); - assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Repository: rust-lang/crates.io'); - assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Workflow: ci.yml'); - assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Environment: release'); - }); - - test('validation errors', async function (assert) { - let { crate } = prepare(this); - - await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); - - // Submit the form without filling in required fields - await click('[data-test-add]'); - - // Check that validation errors are displayed - assert.dom('[data-test-repository-owner-group] [data-test-error]').exists(); - assert.dom('[data-test-repository-name-group] [data-test-error]').exists(); - assert.dom('[data-test-workflow-filename-group] [data-test-error]').exists(); - - // Fill in the required fields - await fillIn('[data-test-repository-owner]', 'rust-lang'); - await fillIn('[data-test-repository-name]', 'crates.io'); - await fillIn('[data-test-workflow-filename]', 'ci.yml'); - - // Submit the form - await click('[data-test-add]'); - - // Check that we're redirected back to the crate settings page - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); - }); - - test('loading and error state', async function (assert) { - let { crate } = prepare(this); - - // Mock the server to return an error - let deferred = defer(); - this.worker.use(http.post('/api/v1/trusted_publishing/github_configs', () => deferred.promise)); - - await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); - - // Fill in the form - await fillIn('[data-test-repository-owner]', 'rust-lang'); - await fillIn('[data-test-repository-name]', 'crates.io'); - await fillIn('[data-test-workflow-filename]', 'ci.yml'); - - // Submit the form - let clickPromise = click('[data-test-add]'); - await waitFor('[data-test-add] [data-test-spinner]'); - assert.dom('[data-test-publisher]').isDisabled(); - assert.dom('[data-test-repository-owner]').isDisabled(); - assert.dom('[data-test-repository-name]').isDisabled(); - assert.dom('[data-test-workflow-filename]').isDisabled(); - assert.dom('[data-test-environment]').isDisabled(); - assert.dom('[data-test-add]').isDisabled(); - - // Resolve the deferred with an error - deferred.resolve(HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 })); - await clickPromise; - - // Check that the error notification is displayed - assert - .dom('[data-test-notification-message]') - .hasText('An error has occurred while adding the Trusted Publishing configuration: Server error'); - - assert.dom('[data-test-publisher]').isEnabled(); - assert.dom('[data-test-repository-owner]').isEnabled(); - assert.dom('[data-test-repository-name]').isEnabled(); - assert.dom('[data-test-workflow-filename]').isEnabled(); - assert.dom('[data-test-environment]').isEnabled(); - assert.dom('[data-test-add]').isEnabled(); - - await click('[data-test-cancel]'); - assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); - assert.dom('[data-test-github-config]').exists({ count: 0 }); - }); - test('cancel button', async function (assert) { let { crate } = prepare(this); @@ -196,48 +65,55 @@ module('Route | crate.settings.new-trusted-publisher', hooks => { { name: 'simple https', url: 'https://github.com/rust-lang/crates.io', + publisher: 'GitHub', owner: 'rust-lang', repo: 'crates.io', }, { name: 'with .git suffix', url: 'https://github.com/rust-lang/crates.io.git', + publisher: 'GitHub', owner: 'rust-lang', repo: 'crates.io', }, { name: 'with extra path segments', url: 'https://github.com/Byron/google-apis-rs/tree/main/gen/privateca1', + publisher: 'GitHub', owner: 'Byron', repo: 'google-apis-rs', }, { name: 'non-github url', url: 'https://gitlab.com/rust-lang/crates.io', + publisher: 'GitHub', owner: '', repo: '', }, { name: 'not a url', url: 'not a url', + publisher: 'GitHub', owner: '', repo: '', }, { name: 'empty string', url: '', + publisher: 'GitHub', owner: '', repo: '', }, { name: 'null', url: null, + publisher: 'GitHub', owner: '', repo: '', }, ]; - for (let { name, url, owner, repo } of testCases) { + for (let { name, url, publisher, owner, repo } of testCases) { test(name, async function (assert) { let { crate } = prepare(this); this.db.crate.update({ @@ -247,88 +123,226 @@ module('Route | crate.settings.new-trusted-publisher', hooks => { await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); + assert.dom('[data-test-publisher]').hasValue(publisher); assert.dom('[data-test-repository-owner]').hasValue(owner); assert.dom('[data-test-repository-name]').hasValue(repo); }); } }); - module('workflow verification', function () { - test('success case (200 OK)', async function (assert) { + module('GitHub', function () { + test('happy path', async function (assert) { let { crate } = prepare(this); - await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); + this.db.trustpubGithubConfig.create({ + crate, + repository_owner: 'johndoe', + repository_name: 'crates.io', + workflow_filename: 'release.yml', + }); + + await visit(`/crates/${crate.name}/settings`); + await click('[data-test-add-trusted-publisher-button]'); assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); - this.worker.use( - http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { - return new HttpResponse(null, { status: 200 }); - }), - ); + await percySnapshot(assert); - assert - .dom('[data-test-workflow-verification="initial"]') - .hasText('The workflow filename will be verified once all necessary fields are filled.'); + // Check that the form is displayed correctly + assert.dom('[data-test-publisher]').exists(); + assert.dom('[data-test-repository-owner]').exists(); + assert.dom('[data-test-repository-name]').exists(); + assert.dom('[data-test-workflow-filename]').exists(); + assert.dom('[data-test-environment]').exists(); + assert.dom('[data-test-add]').exists(); + assert.dom('[data-test-cancel]').exists(); + // Fill in the form await fillIn('[data-test-repository-owner]', 'rust-lang'); await fillIn('[data-test-repository-name]', 'crates.io'); await fillIn('[data-test-workflow-filename]', 'ci.yml'); + await fillIn('[data-test-environment]', 'release'); + + // Submit the form + await click('[data-test-add]'); + + // Check that we're redirected back to the crate settings page + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); - await waitFor('[data-test-workflow-verification="success"]'); + // Check that the config was created + let config = this.db.trustpubGithubConfig.findFirst({ + where: { + repository_owner: { equals: 'rust-lang' }, + repository_name: { equals: 'crates.io' }, + workflow_filename: { equals: 'ci.yml' }, + environment: { equals: 'release' }, + }, + }); + assert.ok(config, 'Config was created'); + + // Check that the success notification is displayed + assert.dom('[data-test-notification-message]').hasText('Trusted Publishing configuration added successfully'); - let expected = - '✓ Workflow file found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml'; - assert.dom('[data-test-workflow-verification="success"]').hasText(expected); + // Check that the config is displayed on the crate settings page + assert.dom('[data-test-github-config]').exists({ count: 2 }); + assert.dom('[data-test-github-config="2"] td:nth-child(1)').hasText('GitHub'); + assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Repository: rust-lang/crates.io'); + assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Workflow: ci.yml'); + assert.dom('[data-test-github-config="2"] td:nth-child(2)').includesText('Environment: release'); }); - test('not found case (404)', async function (assert) { + test('validation errors', async function (assert) { let { crate } = prepare(this); await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); - this.worker.use( - http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml', () => { - return new HttpResponse(null, { status: 404 }); - }), - ); + // Submit the form without filling in required fields + await click('[data-test-add]'); + + // Check that validation errors are displayed + assert.dom('[data-test-repository-owner-group] [data-test-error]').exists(); + assert.dom('[data-test-repository-name-group] [data-test-error]').exists(); + assert.dom('[data-test-workflow-filename-group] [data-test-error]').exists(); + // Fill in the required fields await fillIn('[data-test-repository-owner]', 'rust-lang'); await fillIn('[data-test-repository-name]', 'crates.io'); - await fillIn('[data-test-workflow-filename]', 'missing.yml'); - - await waitFor('[data-test-workflow-verification="not-found"]'); - - let expected = - '⚠ Workflow file not found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml'; - assert.dom('[data-test-workflow-verification="not-found"]').hasText(expected); + await fillIn('[data-test-workflow-filename]', 'ci.yml'); - // Verify form can still be submitted + // Submit the form await click('[data-test-add]'); + + // Check that we're redirected back to the crate settings page assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); }); - test('server error (5xx)', async function (assert) { + test('loading and error state', async function (assert) { let { crate } = prepare(this); + // Mock the server to return an error + let deferred = defer(); + this.worker.use(http.post('/api/v1/trusted_publishing/github_configs', () => deferred.promise)); + await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); - this.worker.use( - http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { - return new HttpResponse(null, { status: 500 }); - }), - ); - + // Fill in the form await fillIn('[data-test-repository-owner]', 'rust-lang'); await fillIn('[data-test-repository-name]', 'crates.io'); await fillIn('[data-test-workflow-filename]', 'ci.yml'); - await waitFor('[data-test-workflow-verification="error"]'); + // Submit the form + let clickPromise = click('[data-test-add]'); + await waitFor('[data-test-add] [data-test-spinner]'); + assert.dom('[data-test-publisher]').isDisabled(); + assert.dom('[data-test-repository-owner]').isDisabled(); + assert.dom('[data-test-repository-name]').isDisabled(); + assert.dom('[data-test-workflow-filename]').isDisabled(); + assert.dom('[data-test-environment]').isDisabled(); + assert.dom('[data-test-add]').isDisabled(); + + // Resolve the deferred with an error + deferred.resolve(HttpResponse.json({ errors: [{ detail: 'Server error' }] }, { status: 500 })); + await clickPromise; + + // Check that the error notification is displayed + assert + .dom('[data-test-notification-message]') + .hasText('An error has occurred while adding the Trusted Publishing configuration: Server error'); + + assert.dom('[data-test-publisher]').isEnabled(); + assert.dom('[data-test-repository-owner]').isEnabled(); + assert.dom('[data-test-repository-name]').isEnabled(); + assert.dom('[data-test-workflow-filename]').isEnabled(); + assert.dom('[data-test-environment]').isEnabled(); + assert.dom('[data-test-add]').isEnabled(); + + await click('[data-test-cancel]'); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); + assert.dom('[data-test-github-config]').exists({ count: 0 }); + }); + + module('workflow verification', function () { + test('success case (200 OK)', async function (assert) { + let { crate } = prepare(this); + + await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); + + this.worker.use( + http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { + return new HttpResponse(null, { status: 200 }); + }), + ); + + assert + .dom('[data-test-workflow-verification="initial"]') + .hasText('The workflow filename will be verified once all necessary fields are filled.'); - let expected = - '⚠ Could not verify workflow file at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml (network error)'; - assert.dom('[data-test-workflow-verification="error"]').hasText(expected); + await fillIn('[data-test-repository-owner]', 'rust-lang'); + await fillIn('[data-test-repository-name]', 'crates.io'); + await fillIn('[data-test-workflow-filename]', 'ci.yml'); + + await waitFor('[data-test-workflow-verification="success"]'); + + let expected = + '✓ Workflow file found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml'; + assert.dom('[data-test-workflow-verification="success"]').hasText(expected); + }); + + test('not found case (404)', async function (assert) { + let { crate } = prepare(this); + + await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); + + this.worker.use( + http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml', () => { + return new HttpResponse(null, { status: 404 }); + }), + ); + + await fillIn('[data-test-repository-owner]', 'rust-lang'); + await fillIn('[data-test-repository-name]', 'crates.io'); + await fillIn('[data-test-workflow-filename]', 'missing.yml'); + + await waitFor('[data-test-workflow-verification="not-found"]'); + + let expected = + '⚠ Workflow file not found at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/missing.yml'; + assert.dom('[data-test-workflow-verification="not-found"]').hasText(expected); + + // Verify form can still be submitted + await click('[data-test-add]'); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`); + }); + + test('server error (5xx)', async function (assert) { + let { crate } = prepare(this); + + await visit(`/crates/${crate.name}/settings/new-trusted-publisher`); + assert.strictEqual(currentURL(), `/crates/${crate.name}/settings/new-trusted-publisher`); + + this.worker.use( + http.head('https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml', () => { + return new HttpResponse(null, { status: 500 }); + }), + ); + + await fillIn('[data-test-repository-owner]', 'rust-lang'); + await fillIn('[data-test-repository-name]', 'crates.io'); + await fillIn('[data-test-workflow-filename]', 'ci.yml'); + + await waitFor('[data-test-workflow-verification="error"]'); + + let expected = + '⚠ Could not verify workflow file at https://raw.githubusercontent.com/rust-lang/crates.io/HEAD/.github/workflows/ci.yml (network error)'; + assert.dom('[data-test-workflow-verification="error"]').hasText(expected); + }); }); }); + + module('GitLab', function () { + // Placeholder for GitLab tests when they are implemented + }); });