| 1 | import * as AWS from "@/AWS"; |
| 2 | import { Enabler } from "@/AWS/Inspector2/Enabler.ts"; |
| 3 | import * as Provider from "@/Provider"; |
| 4 | import * as Test from "@/Test/Alchemy"; |
| 5 | import * as inspector2 from "@distilled.cloud/aws/inspector2"; |
| 6 | import { expect } from "alchemy-test"; |
| 7 | import * as Effect from "effect/Effect"; |
| 8 | |
| 9 | const { test } = Test.make({ providers: AWS.providers() }); |
| 10 | |
| 11 | const accountStatus = inspector2 |
| 12 | .batchGetAccountStatus({}) |
| 13 | .pipe(Effect.map((r) => r.accounts?.[0])); |
| 14 | |
| 15 | const typeStatus = ( |
| 16 | account: inspector2.AccountState | undefined, |
| 17 | key: "ec2" | "ecr" | "lambda", |
| 18 | ) => account?.resourceState?.[key]?.status; |
| 19 | |
| 20 | test.provider( |
| 21 | "account scan status is observable", |
| 22 | () => |
| 23 | Effect.gen(function* () { |
| 24 | const account = yield* accountStatus; |
| 25 | expect(account?.accountId).toBeTruthy(); |
| 26 | expect( |
| 27 | ["ENABLED", "ENABLING", "DISABLED", "DISABLING"].includes( |
| 28 | typeStatus(account, "ec2") ?? "", |
| 29 | ), |
| 30 | ).toBe(true); |
| 31 | expect( |
| 32 | ["ENABLED", "ENABLING", "DISABLED", "DISABLING"].includes( |
| 33 | typeStatus(account, "ecr") ?? "", |
| 34 | ), |
| 35 | ).toBe(true); |
| 36 | }), |
| 37 | { tags: ["provider:aws", "provider:aws:inspector2", "live"] }, |
| 38 | ); |
| 39 | |
| 40 | // The Inspector enabler is an account/region singleton. This test only runs |
| 41 | // when Inspector is fully disabled — it must never disable scan types the user |
| 42 | // already enabled (capture-and-restore safety). |
| 43 | test.provider.skipIf(!process.env.INSPECTOR2_TEST_ENABLER)( |
| 44 | "lifecycle: enable EC2/ECR, add LAMBDA, disable", |
| 45 | (stack) => |
| 46 | Effect.gen(function* () { |
| 47 | const preexisting = yield* accountStatus; |
| 48 | if (preexisting && preexisting.state?.status !== "DISABLED") { |
| 49 | yield* Effect.logInfo( |
| 50 | `Inspector already enabled (${preexisting.state?.status}) — skipping destructive lifecycle test`, |
| 51 | ); |
| 52 | return; |
| 53 | } |
| 54 | |
| 55 | yield* stack.destroy(); |
| 56 | |
| 57 | // Create — enable EC2 + ECR scanning. |
| 58 | const created = yield* stack.deploy( |
| 59 | Effect.gen(function* () { |
| 60 | return yield* Enabler("Inspector", { |
| 61 | resourceTypes: ["EC2", "ECR"], |
| 62 | }); |
| 63 | }), |
| 64 | ); |
| 65 | expect(created.accountId).toBeTruthy(); |
| 66 | expect(created.resourceTypes.sort()).toEqual(["EC2", "ECR"]); |
| 67 | |
| 68 | // Out-of-band verification. |
| 69 | const live = yield* accountStatus; |
| 70 | expect(typeStatus(live, "ec2")).toBe("ENABLED"); |
| 71 | expect(typeStatus(live, "ecr")).toBe("ENABLED"); |
| 72 | |
| 73 | // Canonical list() coverage. |
| 74 | const provider = yield* Provider.findProvider(Enabler); |
| 75 | const all = yield* provider.list(); |
| 76 | expect(all.some((e) => e.accountId === created.accountId)).toBe(true); |
| 77 | |
| 78 | // Update — add LAMBDA scanning. |
| 79 | const updated = yield* stack.deploy( |
| 80 | Effect.gen(function* () { |
| 81 | return yield* Enabler("Inspector", { |
| 82 | resourceTypes: ["EC2", "ECR", "LAMBDA"], |
| 83 | }); |
| 84 | }), |
| 85 | ); |
| 86 | expect(updated.resourceTypes.sort()).toEqual(["EC2", "ECR", "LAMBDA"]); |
| 87 | const afterUpdate = yield* accountStatus; |
| 88 | expect(typeStatus(afterUpdate, "lambda")).toBe("ENABLED"); |
| 89 | |
| 90 | // Destroy — Inspector scanning is disabled again. |
| 91 | yield* stack.destroy(); |
| 92 | const after = yield* accountStatus; |
| 93 | expect(typeStatus(after, "ec2")).not.toBe("ENABLED"); |
| 94 | expect(typeStatus(after, "ecr")).not.toBe("ENABLED"); |
| 95 | expect(typeStatus(after, "lambda")).not.toBe("ENABLED"); |
| 96 | }), |
| 97 | { |
| 98 | tags: ["provider:aws", "provider:aws:inspector2", "live"], |
| 99 | timeout: 240_000, |
| 100 | }, |
| 101 | ); |