mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Stack: 1. #26575 `fix(site/e2e): close mock external-auth servers in teardown` 2. #26793 `fix(site/e2e): accept 404 from external auth reset hook` ← this PR 3. #26795 `fix(site/src): refresh provider state after device-flow exchange` 4. #26798 `fix(site/e2e): reset both providers in external auth hook` 5. #26648 `chore(site/e2e): re-enable externalAuth suite` `deleteExternalAuthByID` used to be inverted: `sql.ErrNoRows` (link doesn't exist for this user/provider) fell through to the `500` path, while non-`ErrNoRows` DB errors went to `httpapi.ResourceNotFound`. #19775 (Sep 2025) refactored it to return `404` for not-found and `500` for real DB errors, which is the contract you'd expect. The relevant lines from #19775 in `coderd/externalauth.go`: ```diff - err := api.Database.DeleteExternalAuthLink(ctx, ...) + link, err := api.Database.GetExternalAuthLink(ctx, ...) if err != nil { - if !errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(w) return } httpapi.Write(ctx, w, http.StatusInternalServerError, ...) return } ``` `resetExternalAuthKey` in `site/e2e/hooks.ts` still treats `500` as the not-found code, so the first `beforeEach` in the externalAuth suite throws. The suite was skipped at the time #19775 landed (#17235), so nobody noticed the contract drift until #26648 tried to re-enable it. This just flips the accepted status codes to `200 || 404` and rewrites the stale comment. The 401/403/500 paths still surface as failures, which is what we want. Refs https://linear.app/codercom/issue/DEVEX-413 Refs https://github.com/coder/coder/pull/19775 <details> <summary>Why a separate PR</summary> Keeps the bisection signal clean: #26575 proves the EADDRINUSE flake is fixed, this PR fixes the hook contract drift surfaced by re-enabling the suite, and #26648 just flips `.skip`. Squashing into #26648 would conflate two unrelated fixes. The CI run on #26648 already confirms the flake fix is doing its job: `successful external auth from workspace` passes (5.6s) and the `beforeAll`/`afterAll` mock servers come up and tear down cleanly with no EADDRINUSE. The only failures are this 404 hook drift. </details>
97 lines
2.5 KiB
TypeScript
97 lines
2.5 KiB
TypeScript
import http from "node:http";
|
|
import type { BrowserContext, Page } from "@playwright/test";
|
|
import { coderPort, gitAuth } from "./constants";
|
|
|
|
export const beforeCoderTest = (page: Page) => {
|
|
page.on("console", (msg) => {
|
|
const location = msg.location();
|
|
// Filters out a bunch of junk warnings the browser produces.
|
|
if (!location.url) {
|
|
return;
|
|
}
|
|
// Filters out the gigantic CODER logo we print on every page load, as well
|
|
// as some other noise.
|
|
if (msg.type() === "info") {
|
|
return;
|
|
}
|
|
console.info(`[console][${msg.type()}] ${msg.text()}`);
|
|
});
|
|
|
|
page.on("response", async (response) => {
|
|
// Don't log responses for static assets.
|
|
if (!isApiCall(response.url())) {
|
|
return;
|
|
}
|
|
// Don't log successful responses. Those are almost always less interesting.
|
|
if (response.ok()) {
|
|
return;
|
|
}
|
|
|
|
let responseText: string;
|
|
try {
|
|
responseText = await response.text();
|
|
responseText = responseText.replaceAll("\n", "");
|
|
} catch {
|
|
responseText = "<n/a>";
|
|
}
|
|
|
|
console.info(
|
|
`[response] url=${response.url()} status=${response.status()} body=${responseText}`,
|
|
);
|
|
});
|
|
|
|
page.on("popup", async (popup) => {
|
|
console.info(`[popup] url=${popup.url()}`);
|
|
});
|
|
|
|
page.on("pageerror", async (error) => {
|
|
console.error("[pageerror]", error);
|
|
});
|
|
|
|
page.on("crash", async (page) => {
|
|
console.error("[crash]", page.url());
|
|
});
|
|
};
|
|
|
|
export const resetExternalAuthKey = async (context: BrowserContext) => {
|
|
// Find the session token so we can destroy the external auth link between tests, to ensure valid authentication happens each time.
|
|
const cookies = await context.cookies();
|
|
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
|
const options = {
|
|
method: "DELETE",
|
|
hostname: "127.0.0.1",
|
|
port: coderPort,
|
|
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
|
};
|
|
|
|
const req = http.request(options, (res) => {
|
|
let data = "";
|
|
res.on("data", (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on("end", () => {
|
|
// 200 = link deleted; 404 = no link existed for this provider.
|
|
if (res.statusCode !== 200 && res.statusCode !== 404) {
|
|
console.error("failed to delete external auth link", data);
|
|
throw new Error(
|
|
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on("error", (err) => {
|
|
throw err.message;
|
|
});
|
|
|
|
req.end();
|
|
};
|
|
|
|
const isApiCall = (urlString: string): boolean => {
|
|
const url = new URL(urlString);
|
|
const apiPath = "/api/v2";
|
|
|
|
return url.pathname.startsWith(apiPath);
|
|
};
|