-
Notifications
You must be signed in to change notification settings - Fork 518
test(cli): cover network-restrictions get and update (CLI-2288)
#6478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
7ttp
wants to merge
4
commits into
develop
Choose a base branch
from
7ttp/cli-2288-network-restrictions-command-family-coverage
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+224
−6
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
dd7e3b4
test: add network-restrictions get live coverage
7ttp 4a59b4a
test: add network-restrictions update live coverage
7ttp c1e0891
test: bound the network-restrictions live restore
7ttp 9cd12b7
test: restore the captured network-restrictions allowlist exactly
7ttp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
apps/cli/src/legacy/commands/network-restrictions/get/get.live.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { expect } from "vitest"; | ||
|
|
||
| import { | ||
| experimentalProjectLiveFlags, | ||
| requireLiveJson, | ||
| test, | ||
| } from "../../../../../tests/helpers/live.ts"; | ||
|
|
||
| test("reads the network restrictions of the target project", async ({ cli, project }) => { | ||
| const result = await cli([ | ||
| "network-restrictions", | ||
| "get", | ||
| ...experimentalProjectLiveFlags(project), | ||
| "-o", | ||
| "json", | ||
| ]); | ||
| expect(result.exitCode, result.stderr).toBe(0); | ||
| expect(requireLiveJson(result, "network-restrictions get"), result.stdout).toMatchObject({ | ||
| entitlement: expect.stringMatching(/^(?:allowed|disallowed)$/u), | ||
| config: expect.any(Object), | ||
| status: expect.stringMatching(/^(?:stored|applied)$/u), | ||
| }); | ||
| }); |
186 changes: 186 additions & 0 deletions
186
apps/cli/src/legacy/commands/network-restrictions/update/update.live.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| import { Schema } from "effect"; | ||
| import { expect } from "vitest"; | ||
|
|
||
| import { | ||
| experimentalProjectLiveFlags, | ||
| type LiveFixtures, | ||
| requireLiveJson, | ||
| requireLiveSuccess, | ||
| test, | ||
| throwWithCleanup, | ||
| } from "../../../../../tests/helpers/live.ts"; | ||
|
|
||
| type LiveCli = LiveFixtures["cli"]; | ||
| type LiveRun = Awaited<ReturnType<LiveCli>>; | ||
|
|
||
| // Every subprocess is bounded and the test's own timeout covers the longest | ||
| // path through them: four 60s commands (the restore is issued at most twice) | ||
| // plus two proof polls that can each run 102s (a 60s deadline that still | ||
| // finishes an in-flight 20s attempt, waits the 2s interval and runs one last | ||
| // 20s attempt), 444s in all, on top of the workspace fixture's own 60s init. | ||
| // Once a test has timed out its fixtures are disposed, so a late restore | ||
| // cannot take effect and the shared project stays locked down. | ||
| const EXIT_TIMEOUT_MS = 60_000; | ||
| const POLL_ATTEMPT_EXIT_TIMEOUT_MS = 20_000; | ||
| const PROOF_TIMEOUT_MS = 60_000; | ||
| const PROOF_INTERVAL_MS = 2_000; | ||
| const LIVE_TIMEOUT_MS = 600_000; | ||
|
|
||
| interface AllowedCidrs { | ||
| readonly v4: ReadonlyArray<string>; | ||
| readonly v6: ReadonlyArray<string>; | ||
| } | ||
|
|
||
| // Documentation ranges (RFC 5737 TEST-NET-3, RFC 3849): public, so the local | ||
| // private-range check accepts them, and unroutable, so allowing them admits | ||
| // nobody while the project is restricted. | ||
| const TEST_CIDRS: AllowedCidrs = { v4: ["203.0.113.0/24"], v6: ["2001:db8::/32"] }; | ||
|
|
||
| // The allow-all sentinels `config.toml` ships as the `db.network_restrictions` | ||
| // defaults; ADR 0022 treats them as the platform's unconfigured state. | ||
| const ALLOW_ALL_CIDRS: AllowedCidrs = { v4: ["0.0.0.0/0"], v6: ["::/0"] }; | ||
|
|
||
| const NetworkRestrictions = Schema.Struct({ | ||
| config: Schema.Struct({ | ||
| dbAllowedCidrs: Schema.optionalKey(Schema.Array(Schema.String)), | ||
| dbAllowedCidrsV6: Schema.optionalKey(Schema.Array(Schema.String)), | ||
| }), | ||
| status: Schema.Literals(["stored", "applied"]), | ||
| }); | ||
|
|
||
| interface Posture { | ||
| readonly cidrs: AllowedCidrs; | ||
| readonly applied: boolean; | ||
| } | ||
|
|
||
| function updateArgs(cidrs: AllowedCidrs, flags: ReadonlyArray<string>): string[] { | ||
| return [ | ||
| "network-restrictions", | ||
| "update", | ||
| ...[...cidrs.v4, ...cidrs.v6].flatMap((cidr) => ["--db-allow-cidr", cidr]), | ||
| ...flags, | ||
| ]; | ||
| } | ||
|
|
||
| function describeAttempt(attempt: number, result: LiveRun): string { | ||
| return `\nattempt ${attempt} (exit ${result.exitCode})\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`; | ||
| } | ||
|
|
||
| // A family the platform leaves absent has nothing to restore, so it reads as | ||
| // `[]` like an explicitly empty one. | ||
| async function readPosture( | ||
| cli: LiveCli, | ||
| flags: ReadonlyArray<string>, | ||
| label: string, | ||
| exitTimeoutMs: number, | ||
| ): Promise<Posture> { | ||
| const result = await cli(["network-restrictions", "get", ...flags, "-o", "json"], { | ||
| exitTimeoutMs, | ||
| }); | ||
| requireLiveSuccess(result, label); | ||
| const payload = requireLiveJson(result, label); | ||
| if (!Schema.is(NetworkRestrictions)(payload)) { | ||
| throw new Error( | ||
| `${label}: unexpected network-restrictions get payload\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`, | ||
| ); | ||
| } | ||
| return { | ||
| cidrs: { | ||
| v4: payload.config.dbAllowedCidrs ?? [], | ||
| v6: payload.config.dbAllowedCidrsV6 ?? [], | ||
| }, | ||
| applied: payload.status === "applied", | ||
| }; | ||
| } | ||
|
|
||
| // get reports `status: "stored"` until a requested allowlist has propagated | ||
| // (see the `V1GetNetworkRestrictionsOutput` config annotation in | ||
| // packages/api), so proving an update or a restore means polling get until | ||
| // the requested allowlist is reported as applied. | ||
| function expectApplied( | ||
| cli: LiveCli, | ||
| flags: ReadonlyArray<string>, | ||
| cidrs: AllowedCidrs, | ||
| label: string, | ||
| ): Promise<void> { | ||
| return expect | ||
| .poll(() => readPosture(cli, flags, label, POLL_ATTEMPT_EXIT_TIMEOUT_MS), { | ||
| interval: PROOF_INTERVAL_MS, | ||
| timeout: PROOF_TIMEOUT_MS, | ||
| message: label, | ||
| }) | ||
| .toEqual({ cidrs, applied: true }); | ||
| } | ||
|
|
||
| test( | ||
| "replaces the allowlist, get proves it, and restores the baseline allowlist", | ||
| { timeout: LIVE_TIMEOUT_MS }, | ||
| async ({ cli, project }) => { | ||
| const flags = experimentalProjectLiveFlags(project); | ||
| const captured = ( | ||
| await readPosture( | ||
| cli, | ||
| flags, | ||
| "network-restrictions get capture for network-restrictions update", | ||
| EXIT_TIMEOUT_MS, | ||
| ) | ||
| ).cidrs; | ||
| // Two empty allowlists (what an unconfigured project reads as) cannot be | ||
| // posted back: update sends both arrays and an empty one is restrict-all, so | ||
| // that baseline is restored as allow-all rather than leaving the shared | ||
| // project locked down for every later live test that reaches the database | ||
| // directly. Any other capture is restored as read, an empty family beside a | ||
| // populated one included. | ||
| const baselineCidrs: AllowedCidrs = | ||
| captured.v4.length === 0 && captured.v6.length === 0 ? ALLOW_ALL_CIDRS : captured; | ||
| let targetError: unknown; | ||
| const cleanupErrors: Array<unknown> = []; | ||
| try { | ||
| const updated = await cli([...updateArgs(TEST_CIDRS, flags), "-o", "json"], { | ||
| exitTimeoutMs: EXIT_TIMEOUT_MS, | ||
| }); | ||
| expect(updated.exitCode, updated.stderr).toBe(0); | ||
| expect(requireLiveJson(updated, "network-restrictions update"), updated.stdout).toMatchObject( | ||
| { config: { dbAllowedCidrs: TEST_CIDRS.v4, dbAllowedCidrsV6: TEST_CIDRS.v6 } }, | ||
| ); | ||
|
|
||
| await expectApplied( | ||
| cli, | ||
| flags, | ||
| TEST_CIDRS, | ||
| "network-restrictions get proof for network-restrictions update", | ||
| ); | ||
| } catch (error) { | ||
| targetError = error; | ||
| } finally { | ||
| try { | ||
| // One re-issue covers a restore that failed transiently. The proof runs | ||
| // whatever the restore reported: it alone shows whether the allowlist | ||
| // came back, and a restore killed while its request was still in flight | ||
| // exits non-zero after the platform may already have applied it. | ||
| const restore = () => | ||
| cli(updateArgs(baselineCidrs, flags), { exitTimeoutMs: EXIT_TIMEOUT_MS }); | ||
| const first = await restore(); | ||
| const restored = first.exitCode === 0 ? first : await restore(); | ||
| if (restored.exitCode !== 0) { | ||
| cleanupErrors.push( | ||
| new Error( | ||
| "network-restrictions update restore of the baseline allowlist failed twice" + | ||
| describeAttempt(1, first) + | ||
| describeAttempt(2, restored), | ||
| ), | ||
| ); | ||
| } | ||
| await expectApplied( | ||
| cli, | ||
| flags, | ||
| baselineCidrs, | ||
| "network-restrictions get proof of the restored allowlist for network-restrictions update", | ||
| ); | ||
| } catch (error) { | ||
| cleanupErrors.push(error); | ||
| } | ||
| } | ||
| throwWithCleanup(targetError, cleanupErrors); | ||
| }, | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.