1import * as AWS from "@/AWS";
2import { Scraper, ScraperLoggingConfiguration, Workspace } from "@/AWS/AMP";
3import * as Logs from "@/AWS/Logs";
4import * as Test from "@/Test/Alchemy";
5import * as amp from "@distilled.cloud/aws/amp";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11// Ungated probes: prove the scraper API surface returns the typed errors the
12// Scraper provider relies on (observe/delete both catch
13// ResourceNotFoundException), at near-zero cost.
14test.provider(
15 "describeScraper returns a typed not-found for a nonexistent scraper",
16 () =>
17 Effect.gen(function* () {
18 const error = yield* Effect.flip(
19 amp.describeScraper({
20 scraperId: "s-00000000-0000-0000-0000-000000000000",
21 }),
22 );
23 expect(error._tag).toBe("ResourceNotFoundException");
24 }),
25 { tags: ["provider:aws", "provider:aws:amp", "live"], timeout: 30_000 },
26);
27
28test.provider(
29 "getDefaultScraperConfiguration returns Prometheus YAML",
30 () =>
31 Effect.gen(function* () {
32 const response = yield* amp.getDefaultScraperConfiguration({});
33 const yaml = yield* Effect.sync(() =>
34 new TextDecoder().decode(response.configuration),
35 );
36 expect(yaml).toContain("scrape_configs");
37 }),
38 { tags: ["provider:aws", "provider:aws:amp", "live"], timeout: 30_000 },
39);
40
41// Full scraper lifecycle requires a live Amazon EKS cluster to scrape and
42// takes many minutes to provision (the service creates network interfaces
43// and an IAM role) — far beyond the suite's provisioning budget. Gate it
44// behind an entitled/provisioned environment:
45// AWS_TEST_AMP_SCRAPER=1
46// AWS_TEST_AMP_SCRAPER_CLUSTER_ARN=arn:aws:eks:...:cluster/my-cluster
47// AWS_TEST_AMP_SCRAPER_SUBNET_IDS=subnet-a,subnet-b
48test.provider.skipIf(!process.env.AWS_TEST_AMP_SCRAPER)(
49 "scraper lifecycle (create, update alias, logging, destroy)",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const clusterArn = process.env.AWS_TEST_AMP_SCRAPER_CLUSTER_ARN!;
55 const subnetIds = process.env.AWS_TEST_AMP_SCRAPER_SUBNET_IDS!.split(",");
56 const defaultConfig = yield* amp.getDefaultScraperConfiguration({});
57 const scrapeConfiguration = yield* Effect.sync(() =>
58 new TextDecoder().decode(defaultConfig.configuration),
59 );
60
61 const program = (alias: string) =>
62 Effect.gen(function* () {
63 const workspace = yield* Workspace("ScraperWorkspace", {
64 alias: "alchemy-test-amp-scraper",
65 tags: { Environment: "test" },
66 });
67 const scraper = yield* Scraper("Scraper", {
68 alias,
69 scrapeConfiguration,
70 source: { eksConfiguration: { clusterArn, subnetIds } },
71 destinationWorkspaceArn: workspace.workspaceArn,
72 tags: { Environment: "test" },
73 });
74 const logs = yield* Logs.LogGroup("ScraperLogs", {
75 logGroupName: "/aws/vendedlogs/prometheus/alchemy-test-scraper",
76 retention: "1 day",
77 });
78 const logging = yield* ScraperLoggingConfiguration("Logging", {
79 scraperId: scraper.scraperId,
80 logGroupArn: logs.logGroupArn,
81 });
82 return { workspace, scraper, logging };
83 });
84
85 const created = yield* stack.deploy(program("alchemy-test-scraper"));
86 const scraperId = created.scraper.scraperId;
87 expect(scraperId).toMatch(/^s-/);
88 expect(created.scraper.status).toBe("ACTIVE");
89 expect(created.logging.scraperId).toBe(scraperId);
90
91 // Out-of-band verification via distilled.
92 const described = yield* amp.describeScraper({ scraperId });
93 expect(described.scraper.tags?.["alchemy::id"]).toBe("Scraper");
94
95 // In-place alias update — same scraper id survives.
96 const updated = yield* stack.deploy(program("alchemy-test-scraper-2"));
97 expect(updated.scraper.scraperId).toBe(scraperId);
98 expect(updated.scraper.alias).toBe("alchemy-test-scraper-2");
99
100 yield* stack.destroy();
101 }),
102 {
103 tags: ["provider:aws", "provider:aws:amp", "provider:aws:logs", "live"],
104 timeout: 1_800_000,
105 },
106);