mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-19 21:00:40 +08:00
Fix dev CI: docker prune missing template + cross-domain test failures (#1582)
Unbreaks the test workflows that have been red on every dev push since June 4. All root causes trace to direct pushes whose own CI runs already failed (`8b78587da`, `c60016226`, `59daf1321`). > Note: this PR originally also fixed the "Docker Server Build and Push" workflow (missing `@hexclave/template` in the Dockerfiles' `turbo prune` scope), but dev picked up the identical fix via59daf1321while this was open, so the Dockerfile changes dropped out after merging dev back in. ## 1. E2E cross-domain spies — broken since `c60016226` (June 4) `c60016226` renamed `_getCurrentRefreshTokenIdIfSignedIn` → `_fetchCurrentRefreshTokenIdIfSignedIn` in the SDK and template unit tests, but missed the eight `vi.spyOn` calls in `apps/e2e/tests/js/cross-domain-auth.test.ts`. `vi.spyOn` throws on missing properties → all 8 tests failed with `_getCurrentRefreshTokenIdIfSignedIn does not exist`. **Fix:** rename the spies. ## 2. signOut test timeout in all 5 SDK packages — broken since `c60016226` (June 4) The refresh-cookie test added in the same commit writes to a cookie token store, which queues a background trusted-parent-domain lookup. That lookup fetches the unreachable test `baseUrl` with retries while holding `storeLock`'s read lock (via `AsyncStore.setAsync`), so the later signOut test deadlocks on `storeLock.withWriteLock` inside `_signOut` and hits the 5s vitest timeout (passes in isolation, fails when the file runs in order). **Fix:** stub `_getTrustedParentDomain` in the cookie test so the queued task settles immediately. ## 3. "does not await pending auth resolutions" — premise broken by `8b78587da` (June 4), masked by the spy rename `8b78587da` added `nonHostedHandlerNames`, making `afterSignIn` resolve to a local URL instead of the hosted domain. The test redirected to `afterSignIn` from a callback page expecting the nested cross-domain auth params path to run — but the redirect became same-origin, so `_fetchCurrentRefreshTokenIdIfSignedIn` is (correctly) never called. This was invisible until fix 1 above unmasked it. **Fix:** redirect to `accountSettings` (still hosted, so still cross-origin), preserving the test's intent: the session lookup during nested-param construction must not await pending auth resolutions. ## 4. internal-metrics e2e snapshots — broken on dev by `59daf1321` The analytics overview filters PR reshaped the metrics endpoint response (added `bounce_rate`, daily/hourly breakdown arrays, `top_browsers`/`devices`/`operating_systems`/`regions`; slimmed the zero-filled daily fallback arrays) and updated the backend unit-test snapshots, but left the e2e snapshots stale — its own dev run fails these two tests identically. **Fix:** update `__snapshots__/internal-metrics.test.ts.snap`, reconstructed from the CI diff with every context line verified against the old snapshot, and the new fields cross-checked against the route change in59daf1321. ## Verification - `client-app-impl.cross-domain.test.ts`: 7/7 in `packages/template` and the regenerated `packages/js` copy (signOut: 5s timeout → 10ms). - `tests/js/cross-domain-auth.test.ts`: 18/18 locally (fully mocked, no backend needed). - Lint + typecheck pass for the touched packages. - The metrics snapshot can only be fully confirmed by CI (needs the live backend). ## Out of scope "Run setup tests with custom base port" also intermittently fails unrelated test files at exactly 60s under runner load (last green May 5), and the local-emulator run had one `payments/switch-plans` flake — pre-existing flakiness not addressed here.
This commit is contained in:
parent
f4c13db079
commit
64eeedce9f
File diff suppressed because it is too large
Load Diff
@ -293,8 +293,8 @@ it("does not await pending auth resolutions when post-callback redirect adds nes
|
||||
await withHostedDomainSuffix(async () => {
|
||||
const projectId = "13131313-1313-4313-8313-131313131313";
|
||||
const clientApp = createClientApp(projectId);
|
||||
const getCurrentRefreshTokenIdIfSignedInSpy = vi
|
||||
.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn")
|
||||
const fetchCurrentRefreshTokenIdIfSignedInSpy = vi
|
||||
.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn")
|
||||
.mockResolvedValue(null);
|
||||
|
||||
const previousWindow = globalThis.window;
|
||||
@ -310,8 +310,10 @@ it("does not await pending auth resolutions when post-callback redirect adds nes
|
||||
} as any;
|
||||
|
||||
try {
|
||||
// accountSettings (unlike afterSignIn & co, which resolve to local URLs) still lives on the
|
||||
// hosted domain, so it exercises the nested cross-domain auth params path.
|
||||
await expect((clientApp as any)._redirectToHandler(
|
||||
"afterSignIn",
|
||||
"accountSettings",
|
||||
{ replace: true },
|
||||
{ awaitPendingAuthResolutions: false },
|
||||
)).rejects.toThrowError("INTENTIONAL_TEST_ABORT");
|
||||
@ -320,9 +322,9 @@ it("does not await pending auth resolutions when post-callback redirect adds nes
|
||||
globalThis.document = previousDocument;
|
||||
}
|
||||
|
||||
expect(getCurrentRefreshTokenIdIfSignedInSpy).toHaveBeenCalledWith({
|
||||
expect(fetchCurrentRefreshTokenIdIfSignedInSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
awaitPendingAuthResolutions: false,
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@ -446,7 +448,7 @@ it("adds nested cross-domain auth params when redirecting signed-in users to hos
|
||||
const currentHref = `${localRedirectUrl}/dashboard?tab=settings`;
|
||||
const clientApp = createClientApp(projectId);
|
||||
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(refreshTokenId);
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(refreshTokenId);
|
||||
|
||||
const previousWindow = globalThis.window;
|
||||
const previousDocument = globalThis.document;
|
||||
@ -484,7 +486,7 @@ it("adds nested cross-domain auth params for other cross-domain handler redirect
|
||||
const currentHref = `${localRedirectUrl}/private-page`;
|
||||
const clientApp = createClientApp(projectId);
|
||||
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(refreshTokenId);
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(refreshTokenId);
|
||||
|
||||
const previousWindow = globalThis.window;
|
||||
const previousDocument = globalThis.document;
|
||||
@ -524,7 +526,7 @@ it("starts nested cross-domain auth from the target domain", async ({ expect })
|
||||
const previousDocument = globalThis.document;
|
||||
let redirectedUrl = "";
|
||||
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(null);
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(null);
|
||||
vi.spyOn(clientApp as any, "_getCrossDomainHandoffParamsForRedirect").mockResolvedValue({
|
||||
state: "nested-state",
|
||||
codeChallenge: "nested-code-challenge",
|
||||
@ -588,7 +590,7 @@ it("carries hosted sign-in return state on the nested OAuth redirect URI", async
|
||||
const previousDocument = globalThis.document;
|
||||
let redirectedUrl = "";
|
||||
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(null);
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(null);
|
||||
vi.spyOn(clientApp as any, "_getCrossDomainHandoffParamsForRedirect").mockResolvedValue({
|
||||
state: "nested-state",
|
||||
codeChallenge: "nested-code-challenge",
|
||||
@ -649,7 +651,7 @@ it("continues nested cross-domain auth on the source domain", async ({ expect })
|
||||
const createCrossDomainAuthRedirectUrlSpy = vi
|
||||
.spyOn(clientApp as any, "_createCrossDomainAuthRedirectUrl")
|
||||
.mockResolvedValue(crossDomainRedirect);
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(sourceRefreshTokenId);
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(sourceRefreshTokenId);
|
||||
|
||||
globalThis.document = createMockDocument();
|
||||
globalThis.window = {
|
||||
@ -721,7 +723,7 @@ it("rejects nested cross-domain auth when the callback URL is untrusted", async
|
||||
const previousWindow = globalThis.window;
|
||||
const previousDocument = globalThis.document;
|
||||
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(null);
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue(null);
|
||||
vi.spyOn(clientApp as any, "_isTrusted").mockResolvedValue(false);
|
||||
|
||||
globalThis.document = createMockDocument();
|
||||
@ -753,7 +755,7 @@ it("rejects nested cross-domain auth when the source session does not match", as
|
||||
const previousWindow = globalThis.window;
|
||||
const previousDocument = globalThis.document;
|
||||
const createCrossDomainAuthRedirectUrlSpy = vi.spyOn(clientApp as any, "_createCrossDomainAuthRedirectUrl");
|
||||
vi.spyOn(clientApp as any, "_getCurrentRefreshTokenIdIfSignedIn").mockResolvedValue("different-source-session");
|
||||
vi.spyOn(clientApp as any, "_fetchCurrentRefreshTokenIdIfSignedIn").mockResolvedValue("different-source-session");
|
||||
|
||||
globalThis.document = createMockDocument();
|
||||
globalThis.window = {
|
||||
|
||||
@ -283,6 +283,12 @@ describe("StackClientApp cross-domain auth", () => {
|
||||
const originalFetchNewAccessToken = Reflect.get(clientInterface, "fetchNewAccessToken");
|
||||
const refreshedRawRefreshTokens: string[] = [];
|
||||
|
||||
// Cookie-store writes queue a background trusted-parent-domain lookup. Without this stub, that
|
||||
// lookup fetches the (unreachable) baseUrl with retries while holding the global store lock,
|
||||
// which starves any later test that needs the write lock (e.g. signOut). Not restored on
|
||||
// purpose: queued tasks can still run after this test body finishes.
|
||||
vi.spyOn(clientApp as any, "_getTrustedParentDomain").mockResolvedValue(null);
|
||||
|
||||
try {
|
||||
const getBrowserCookieTokenStore = Reflect.get(clientApp, "_getBrowserCookieTokenStore");
|
||||
if (typeof getBrowserCookieTokenStore !== "function") {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user