|
| 1 | +/** |
| 2 | + * Drift guard across the three token-bearing structures behind `--allow`: |
| 3 | + * |
| 4 | + * - `ALLOW_TOKENS` (`lib/args.ts`) — the CLI's authoritative list; what |
| 5 | + * actually VALIDATES `--allow <csv>`. |
| 6 | + * - `AllowTokenEnum` (`sdk`'s `config.ts`) — validates the STATIC |
| 7 | + * `migrate.allow` array in `.metaobjects/config.json`. |
| 8 | + * - `ALLOW_TOKEN_MAP` (`lib/allow.ts`) — what actually GRANTS the permission, |
| 9 | + * translating a validated token into the `AllowOptions` field `diff()` |
| 10 | + * reads. |
| 11 | + * |
| 12 | + * `ALLOW_TOKENS` and `AllowTokenEnum` silently drifted before this test |
| 13 | + * existed: sdk's enum had only 6 of the 11 real tokens, missing |
| 14 | + * `drop-check`, `drop-view`, `drop-view-cascade`, `adopt-view` and |
| 15 | + * `drop-identity-default`. A user who set any of those five in |
| 16 | + * `.metaobjects/config.json`'s `migrate.allow` got a schema rejection for a |
| 17 | + * flag the CLI itself accepted fine on the command line — `adopt-view` had |
| 18 | + * shipped since 0.20.4 and was affected the whole time. |
| 19 | + * |
| 20 | + * `ALLOW_TOKEN_MAP` is a distinct, worse failure mode if it drifts from |
| 21 | + * `ALLOW_TOKENS`: a token present in `ALLOW_TOKENS` (and `AllowTokenEnum`) |
| 22 | + * but missing from the map passes validation cleanly and then |
| 23 | + * `tokensToAllowOptions` silently grants NOTHING for it — the user believes |
| 24 | + * `--allow <token>` authorized a destructive drop; it didn't, and the diff |
| 25 | + * blocks it anyway with no indication the flag was ever a no-op. That is a |
| 26 | + * silent-failure mode on exactly the path this whole feature exists to |
| 27 | + * protect. |
| 28 | + * |
| 29 | + * Import ALL of these rather than hardcoding a fourth "expected" list here — |
| 30 | + * a hardcoded list would just be a fifth copy that can itself drift. |
| 31 | + * |
| 32 | + * Package-dependency direction: `cli` depends on `sdk` (`workspace:*`), not |
| 33 | + * the other way around, so this test can only live in `cli` — `sdk` cannot |
| 34 | + * import from `cli` without introducing a cycle. `sdk`'s `AllowTokenEnum` |
| 35 | + * itself carries a doc comment pointing back at this test as the drift guard, |
| 36 | + * since `sdk` has no test that can perform the comparison from its own side. |
| 37 | + */ |
| 38 | +import { test, expect, describe } from "bun:test"; |
| 39 | +import { ALLOW_TOKENS } from "../../src/lib/args.js"; |
| 40 | +import { ALLOW_TOKEN_MAP } from "../../src/lib/allow.js"; |
| 41 | +import { AllowTokenEnum } from "@metaobjectsdev/sdk"; |
| 42 | + |
| 43 | +describe("--allow token lists stay pinned across packages", () => { |
| 44 | + test("sdk's AllowTokenEnum and the CLI's ALLOW_TOKENS validate the exact same token set", () => { |
| 45 | + const cliTokens = new Set<string>(ALLOW_TOKENS); |
| 46 | + const sdkTokens = new Set<string>(AllowTokenEnum.options); |
| 47 | + |
| 48 | + const missingFromSdk = [...cliTokens].filter((t) => !sdkTokens.has(t)); |
| 49 | + const missingFromCli = [...sdkTokens].filter((t) => !cliTokens.has(t)); |
| 50 | + |
| 51 | + expect(missingFromSdk).toEqual([]); |
| 52 | + expect(missingFromCli).toEqual([]); |
| 53 | + expect(sdkTokens.size).toBe(cliTokens.size); |
| 54 | + }); |
| 55 | + |
| 56 | + test("ALLOW_TOKEN_MAP grants a permission for every validated token, and nothing extra", () => { |
| 57 | + const cliTokens = new Set<string>(ALLOW_TOKENS); |
| 58 | + const mapKeys = new Set<string>(Object.keys(ALLOW_TOKEN_MAP)); |
| 59 | + |
| 60 | + const validatedButNotGranted = [...cliTokens].filter((t) => !mapKeys.has(t)); |
| 61 | + const grantedButNotValidated = [...mapKeys].filter((t) => !cliTokens.has(t)); |
| 62 | + |
| 63 | + expect(validatedButNotGranted).toEqual([]); |
| 64 | + expect(grantedButNotValidated).toEqual([]); |
| 65 | + expect(mapKeys.size).toBe(cliTokens.size); |
| 66 | + }); |
| 67 | + |
| 68 | + test("ALLOW_TOKEN_MAP's AllowOptions fields are unique — no two tokens grant the same permission", () => { |
| 69 | + const fields = Object.values(ALLOW_TOKEN_MAP); |
| 70 | + const uniqueFields = new Set(fields); |
| 71 | + expect(uniqueFields.size).toBe(fields.length); |
| 72 | + }); |
| 73 | +}); |
0 commit comments