mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
test: read the account payload as the release publishes it
Four cases in the accountAccess integration test were written against a contract this release rejected. They asserted an entry of seven fields and a block of three, both of which gained one; and they asserted that a MANAGE grant publishes `access: "manage"`. It does not, by design. `access` is the v1.36.0 name and its union is `read | write` — a client compiled against that release decodes it as a closed set. MANAGE therefore publishes as `write` there and as `manage` on `level`, and the entry schema raises a validation issue unless a manage entry carries exactly legacy write access and whole scope. The expectations move; the schema does not. `recordKind` is required on an entry and on the block. Both are asserted now, including the block-level value moving from `self` to `shared` across a switch, so a value stuck on either reads as two cases disagreeing.
This commit is contained in:
1 parent
5115dfe394
commit
b2e225470b
1 file changed
+33
-3
@@ -70,8 +70,14 @@ interface AccountAccessEntry {
|
||||
accountId: string;
|
||||
username: string;
|
||||
displayName: string | null;
|
||||
access: "read" | "write" | "manage";
|
||||
/**
|
||||
* The legacy name, and deliberately narrower than `level`: it carries only
|
||||
* `read` and `write`, because that is the union v1.36.0 published and
|
||||
* shipped clients still decode. A MANAGE grant is a `write` here.
|
||||
*/
|
||||
access: "read" | "write";
|
||||
level: "read" | "write" | "manage";
|
||||
recordKind: "shared" | "managed";
|
||||
sections: string[] | null;
|
||||
canWrite: boolean;
|
||||
}
|
||||
@@ -79,6 +85,7 @@ interface AccountAccessEntry {
|
||||
interface AccountAccessBlock {
|
||||
accounts: AccountAccessEntry[];
|
||||
active: AccountAccessEntry | null;
|
||||
recordKind: "self" | "shared" | "managed";
|
||||
canSwitch: boolean;
|
||||
}
|
||||
|
||||
@@ -146,6 +153,9 @@ describe("accountAccess — what the payload publishes", () => {
|
||||
expect(accountAccess).toEqual({
|
||||
accounts: [],
|
||||
active: null,
|
||||
// The record in view is this account's own, and the block says so
|
||||
// rather than leaving the client to infer it from `active` being null.
|
||||
recordKind: "self",
|
||||
canSwitch: false,
|
||||
});
|
||||
});
|
||||
@@ -165,6 +175,10 @@ describe("accountAccess — what the payload publishes", () => {
|
||||
displayName: owner.displayName,
|
||||
access: "read",
|
||||
level: "read",
|
||||
// An adult who shared their own record. `managed` is reserved for a
|
||||
// profile that has no login of its own, and the two are not
|
||||
// interchangeable on any surface that renders a name.
|
||||
recordKind: "shared",
|
||||
sections: null,
|
||||
canWrite: false,
|
||||
},
|
||||
@@ -260,11 +274,21 @@ describe("accountAccess — what the payload publishes", () => {
|
||||
expect(accountAccess.accounts[0].sections).toEqual([]);
|
||||
});
|
||||
|
||||
it("publishes a manage grant as manage, and as writable", async () => {
|
||||
it("publishes a manage grant as manage on level, and as write on the legacy name", async () => {
|
||||
// The shipped expression was `access === "WRITE" ? "write" : "read"`,
|
||||
// which would have published a grant that can delete entries as read-only
|
||||
// — the payload understating access is the direction that reads as safe
|
||||
// and is not, because the server would have gone on admitting the writes.
|
||||
//
|
||||
// The correction is on `level`, and only there. `access` is the v1.36.0
|
||||
// name, and its union is `read | write`: a client built against that
|
||||
// release decodes it as a closed set and fails on a third value it has
|
||||
// never seen. So MANAGE publishes as `write` on the legacy name — an
|
||||
// understatement a shipped client can act on, beside the exact answer on
|
||||
// `level` for a client that knows to read it. The narrowing is enforced
|
||||
// rather than incidental: `accountAccessEntrySchema` raises an issue
|
||||
// unless a `manage` entry carries exactly legacy write access and whole
|
||||
// scope (`src/lib/sharing/account-access-schema.ts`).
|
||||
const owner = await makeUser("owner");
|
||||
const delegate = await makeUser("delegate");
|
||||
await grantAccess(owner.id, delegate.id, { access: "MANAGE" });
|
||||
@@ -273,9 +297,10 @@ describe("accountAccess — what the payload publishes", () => {
|
||||
const { accountAccess } = await readMe();
|
||||
|
||||
expect(accountAccess.accounts[0].level).toBe("manage");
|
||||
expect(accountAccess.accounts[0].access).toBe("manage");
|
||||
expect(accountAccess.accounts[0].access).toBe("write");
|
||||
expect(accountAccess.accounts[0].canWrite).toBe(true);
|
||||
expect(accountAccess.accounts[0].sections).toBeNull();
|
||||
expect(accountAccess.accounts[0].recordKind).toBe("shared");
|
||||
});
|
||||
|
||||
it("never lists a grant this account GAVE — the block is about what it may open", async () => {
|
||||
@@ -311,12 +336,17 @@ describe("accountAccess — the active record", () => {
|
||||
displayName: owner.displayName,
|
||||
access: "read",
|
||||
level: "read",
|
||||
recordKind: "shared",
|
||||
sections: null,
|
||||
canWrite: false,
|
||||
});
|
||||
// The active entry is one of the listed ones, by construction. A banner
|
||||
// that had to join the two could render unnamed when they disagreed.
|
||||
expect(accountAccess.accounts).toContainEqual(accountAccess.active);
|
||||
// The block-level kind moves with the switch. It reads `self` in the
|
||||
// empty case above, so a value stuck on one of the two would show up as
|
||||
// one of these two cases disagreeing with the session.
|
||||
expect(accountAccess.recordKind).toBe("shared");
|
||||
});
|
||||
|
||||
it("reads as not-switched when the stamp outlives the grant", async () => {
|
||||
|
||||
Reference in new issue
Block a user