| @@ -12,6 +12,8 @@ import type { Identity, Target as TargetShape } from "../src/target"; · 11 unmodified lines |
| 12 | import type { BrowserSurface } from "../src/surfaces/browser"; | 12 | import type { BrowserSurface } from "../src/surfaces/browser"; |
| 13 | | 13 | |
| 14 | const textEncoder = new TextEncoder(); | 14 | const textEncoder = new TextEncoder(); |
| | 15 | const FIXTURE_GIT_TOKEN = "fixture-token"; |
| | 16 | const EXPECTED_GIT_AUTHORIZATION = `Basic ${btoa(`git:${FIXTURE_GIT_TOKEN}`)}`; |
| 15 | | 17 | |
| 16 | const concat = (parts: readonly Uint8Array[]): Uint8Array => { | 18 | const concat = (parts: readonly Uint8Array[]): Uint8Array => { |
| 17 | const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0)); | 19 | const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0)); |
| @@ -72,6 +74,18 @@ const fixtureGitServer = Effect.acquireRelease( · 54 unmodified lines |
| 72 | let current = { sha: fixture.sha1, pack: new Uint8Array(fixture.pack1) }; | 74 | let current = { sha: fixture.sha1, pack: new Uint8Array(fixture.pack1) }; |
| 73 | let packRequests = 0; | 75 | let packRequests = 0; |
| 74 | const server = createServer((request: IncomingMessage, response: ServerResponse) => { | 76 | const server = createServer((request: IncomingMessage, response: ServerResponse) => { |
| | 77 | if ( |
| | 78 | (request.url === "/repo.git/info/refs?service=git-upload-pack" || |
| | 79 | request.url === "/repo.git/git-upload-pack") && |
| | 80 | request.headers.authorization !== EXPECTED_GIT_AUTHORIZATION |
| | 81 | ) { |
| | 82 | response.writeHead(401, { |
| | 83 | "content-type": "text/plain", |
| | 84 | "www-authenticate": 'Basic realm="custom-tools-fixture"', |
| | 85 | }); |
| | 86 | response.end("authentication required"); |
| | 87 | return; |
| | 88 | } |
| 75 | if (request.url === "/repo.git/info/refs?service=git-upload-pack") { | 89 | if (request.url === "/repo.git/info/refs?service=git-upload-pack") { |
| 76 | response.writeHead(200, { | 90 | response.writeHead(200, { |
| 77 | "content-type": "application/x-git-upload-pack-advertisement", | 91 | "content-type": "application/x-git-upload-pack-advertisement", |
| @@ -208,7 +222,7 @@ const addSourceThroughConsole = (input: { · 130 unmodified lines |
| 208 | }); | 222 | }); |
| 209 | | 223 | |
| 210 | await step("Sync the custom tools source", async () => { | 224 | await step("Sync the custom tools source", async () => { |
| 211 | await page.locator('input[type="password"]').fill("fixture-token"); | 225 | await page.locator('input[type="password"]').fill(FIXTURE_GIT_TOKEN); |
| 212 | await page.getByRole("button", { name: "Sync source" }).click(); | 226 | await page.getByRole("button", { name: "Sync source" }).click(); |
| 213 | await page.waitForURL(/\/integrations\/repo(?:\?|$)/, { timeout: 90_000 }); | 227 | await page.waitForURL(/\/integrations\/repo(?:\?|$)/, { timeout: 90_000 }); |
| 214 | await page.getByLabel("Source").getByText("2 tools").waitFor({ timeout: 90_000 }); | 228 | await page.getByLabel("Source").getByText("2 tools").waitFor({ timeout: 90_000 }); |
| @@ -221,6 +235,47 @@ const addSourceThroughConsole = (input: { · 6 unmodified lines |
| 221 | }); | 235 | }); |
| 222 | }); | 236 | }); |
| 223 | | 237 | |
| | 238 | const assertBadTokenFailureThroughConsole = (input: { |
| | 239 | readonly target: TargetShape; |
| | 240 | readonly browser: BrowserSurface; |
| | 241 | readonly identity: Identity; |
| | 242 | readonly sourceUrl: string; |
| | 243 | }) => |
| | 244 | input.browser.session(input.identity, async ({ page, step }) => { |
| | 245 | await step("Try to sync the custom tools source with a bad token", async () => { |
| | 246 | await page.goto(new URL("/integrations", input.target.baseUrl).toString(), { |
| | 247 | waitUntil: "networkidle", |
| | 248 | }); |
| | 249 | await page.getByRole("button", { name: "Connect", exact: true }).click(); |
| | 250 | const dialog = page.getByRole("dialog"); |
| | 251 | await dialog.getByRole("textbox").fill(input.sourceUrl); |
| | 252 | await dialog.getByRole("button", { name: "Detect" }).click(); |
| | 253 | await page.getByRole("heading", { name: "Add custom tools" }).waitFor(); |
| | 254 | await page.locator('input[type="password"]').fill("wrong-token"); |
| | 255 | const syncResponse = page.waitForResponse( |
| | 256 | (response) => |
| | 257 | response.url().includes("/api/apps/sources/repo/sync") && response.status() === 200, |
| | 258 | { timeout: 90_000 }, |
| | 259 | ); |
| | 260 | await page.getByRole("button", { name: "Sync source" }).click(); |
| | 261 | await syncResponse; |
| | 262 | }); |
| | 263 | |
| | 264 | await step("See the failed sync on the source panel", async () => { |
| | 265 | await page.goto(new URL("/integrations/repo?tab=source", input.target.baseUrl).toString(), { |
| | 266 | waitUntil: "networkidle", |
| | 267 | }); |
| | 268 | await page.getByText("Last sync failed").waitFor({ timeout: 90_000 }); |
| | 269 | await page.getByText("source: info/refs 401").waitFor({ timeout: 90_000 }); |
| | 270 | }); |
| | 271 | |
| | 272 | await step("Remove the failed source before retrying with the good token", async () => { |
| | 273 | await page.getByRole("button", { name: "Remove" }).click(); |
| | 274 | await page.getByRole("button", { name: "Remove source" }).click(); |
| | 275 | await page.waitForURL(/\/integrations(?:\?|$)/, { timeout: 90_000 }); |
| | 276 | }); |
| | 277 | }); |
| | 278 | |
| 224 | const syncSourceInConsole = (input: { | 279 | const syncSourceInConsole = (input: { |
| 225 | readonly target: TargetShape; | 280 | readonly target: TargetShape; |
| 226 | readonly browser: BrowserSurface; | 281 | readonly browser: BrowserSurface; |
| @@ -279,6 +334,20 @@ scenario( · 52 unmodified lines |
| 279 | expect(unauthorized.status, "sync requires authentication").toBe(401); | 334 | expect(unauthorized.status, "sync requires authentication").toBe(401); |
| 280 | yield* Effect.promise(() => unauthorized.text()); | 335 | yield* Effect.promise(() => unauthorized.text()); |
| 281 | | 336 | |
| | 337 | const missingGitAuth = yield* Effect.promise(() => |
| | 338 | fetch(`${git.url}/info/refs?service=git-upload-pack`), |
| | 339 | ); |
| | 340 | expect(missingGitAuth.status, "fixture git server requires auth").toBe(401); |
| | 341 | yield* Effect.promise(() => missingGitAuth.text()); |
| | 342 | |
| | 343 | yield* assertBadTokenFailureThroughConsole({ |
| | 344 | target, |
| | 345 | browser, |
| | 346 | identity, |
| | 347 | sourceUrl: git.url, |
| | 348 | }); |
| | 349 | expect(git.packRequests(), "bad token fails before fetching a pack").toBe(0); |
| | 350 | |
| 282 | yield* addSourceThroughConsole({ target, browser, identity, sourceUrl: git.url }); | 351 | yield* addSourceThroughConsole({ target, browser, identity, sourceUrl: git.url }); |
| 283 | | 352 | |
| 284 | const sources = yield* Effect.promise(() => | 353 | const sources = yield* Effect.promise(() => |