mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
test(aasa): pin payload shape and content-type
Five assertions on the route handler:
- 200 on an unauthenticated GET.
- Content-Type is exactly application/json with no charset parameter
(Apple's swcd/aasa-validator refuse a charset and the failure mode
is silent — Universal Links just stop firing).
- Cache-Control allows public CDN mirroring.
- JSON shape matches the SB-4 spec exactly.
- applinks App ID matches webcredentials App ID so a rotated
Team ID never half-lands.
Includes the v1.4.40 phase report.
This commit is contained in:
1 parent
8d6f1cf8d8
commit
014593a2a1
2 files changed
+158
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
/**
|
||||
* SB-4 (v1.4.40) — Apple App Site Association (AASA) handler contract.
|
||||
*
|
||||
* iOS Universal Links and Web Credentials both fetch
|
||||
* `/.well-known/apple-app-site-association` without credentials and feed
|
||||
* the body to Apple's swcd / aasa-validator. Both pieces of Apple
|
||||
* tooling are strict about three things:
|
||||
*
|
||||
* 1. The exact JSON shape — `applinks.details[].appID` (the
|
||||
* `<TeamID>.<BundleID>` prefix) plus `applinks.details[].paths`
|
||||
* (the URL-prefix allow-list), and `webcredentials.apps` (the same
|
||||
* App ID prefix) for passkey origin-sharing.
|
||||
* 2. The `Content-Type` header — must be `application/json` *without*
|
||||
* a `charset` parameter. `application/json; charset=utf-8` makes
|
||||
* Apple's CDN proxy refuse to mirror the file, which silently
|
||||
* breaks Universal Links on every device.
|
||||
* 3. The response answers 200 to an unauthenticated GET (covered by
|
||||
* `proxy-well-known-public.test.ts` at the proxy layer).
|
||||
*
|
||||
* This file pins (1) and (2). Any drift means iOS PB30 (App-Store
|
||||
* submission) regresses without a CI signal.
|
||||
*/
|
||||
|
||||
import { GET } from "@/app/.well-known/apple-app-site-association/route";
|
||||
|
||||
const APP_ID = "S8WDX4W5KX.dev.healthlog.app";
|
||||
|
||||
describe("/.well-known/apple-app-site-association (SB-4)", () => {
|
||||
it("answers 200", async () => {
|
||||
const res = GET();
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("uses Content-Type: application/json with no charset", async () => {
|
||||
const res = GET();
|
||||
const contentType = res.headers.get("content-type");
|
||||
// Apple's aasa-validator + swcd reject any `charset=…` parameter
|
||||
// on the AASA file. The handler MUST emit the bare media type.
|
||||
expect(contentType).toBe("application/json");
|
||||
});
|
||||
|
||||
it("sets a Cache-Control directive that allows mirroring", async () => {
|
||||
const res = GET();
|
||||
const cacheControl = res.headers.get("cache-control");
|
||||
expect(cacheControl).toBeTruthy();
|
||||
// Public + max-age so Apple's CDN (one-hour TTL) can mirror the
|
||||
// file without falling back to private/no-store behaviour.
|
||||
expect(cacheControl).toMatch(/public/);
|
||||
expect(cacheControl).toMatch(/max-age=\d+/);
|
||||
});
|
||||
|
||||
it("emits the SB-4 payload shape exactly", async () => {
|
||||
const res = GET();
|
||||
const body = await res.json();
|
||||
expect(body).toEqual({
|
||||
applinks: {
|
||||
details: [
|
||||
{
|
||||
appID: APP_ID,
|
||||
paths: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
webcredentials: {
|
||||
apps: [APP_ID],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the same App ID prefix for applinks and webcredentials", async () => {
|
||||
// Apple's passkey origin-pairing requires the iOS bundle and the
|
||||
// website origin to advertise the same App ID. Splitting these two
|
||||
// entries (e.g. a stale Team ID on one side) breaks passkey login
|
||||
// without a redirect / status-code signal — the only symptom is a
|
||||
// silent fall-back to password login on iOS.
|
||||
const res = GET();
|
||||
const body = (await res.json()) as {
|
||||
applinks: { details: Array<{ appID: string }> };
|
||||
webcredentials: { apps: string[] };
|
||||
};
|
||||
expect(body.applinks.details[0].appID).toBe(body.webcredentials.apps[0]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user