1import * as AWS from "@/AWS";
2import { InstrumentationConfiguration } from "@/AWS/ApplicationSignals";
3import * as Test from "@/Test/Alchemy";
4import * as appsignals from "@distilled.cloud/aws/application-signals";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10// The instrumented service does not need to exist — configurations are
11// stored server-side and picked up by SDK agents on their next sync.
12const SERVICE = "alchemy-test-appsignals-ic";
13const ENVIRONMENT = "alchemy-test-env";
14
15const location = (lineNumber: number): appsignals.CodeLocation => ({
16 Language: "Python",
17 CodeUnit: "app.main",
18 MethodName: "handler",
19 FilePath: "app/main.py",
20 LineNumber: lineNumber,
21});
22
23const getConfiguration = (locationHash: string) =>
24 appsignals
25 .getInstrumentationConfiguration({
26 InstrumentationType: "PROBE",
27 Service: SERVICE,
28 Environment: ENVIRONMENT,
29 SignalType: "SNAPSHOT",
30 LocationIdentifier: { LocationHash: locationHash },
31 })
32 .pipe(Effect.map((r) => r.Configuration));
33
34const assertGone = (locationHash: string) =>
35 getConfiguration(locationHash).pipe(
36 Effect.flatMap((c) =>
37 Effect.fail(new Error(`configuration '${c.LocationHash}' still exists`)),
38 ),
39 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
40 );
41
42test.provider(
43 "create, update tags in place, replace on location change, destroy",
44 (stack) =>
45 Effect.gen(function* () {
46 yield* stack.destroy();
47
48 const props = {
49 instrumentationType: "PROBE",
50 service: SERVICE,
51 environment: ENVIRONMENT,
52 signalType: "SNAPSHOT",
53 captureConfiguration: {
54 CaptureLocals: ["x"],
55 CaptureLimits: { MaxHits: 5 },
56 } satisfies appsignals.CodeCaptureConfiguration,
57 };
58
59 // CREATE
60 const { probe } = yield* stack.deploy(
61 Effect.gen(function* () {
62 const probe = yield* InstrumentationConfiguration("Probe", {
63 ...props,
64 location: location(10),
65 description: "alchemy instrumentation configuration test",
66 tags: { fixture: "application-signals-ic" },
67 });
68 return { probe };
69 }),
70 );
71
72 expect(probe.arn).toContain(":instrumentationConfig/");
73 expect(probe.locationHash).toBeTruthy();
74 expect(probe.service).toBe(SERVICE);
75
76 const observed = yield* getConfiguration(probe.locationHash);
77 expect(observed.ARN).toBe(probe.arn);
78 expect(observed.Description).toBe(
79 "alchemy instrumentation configuration test",
80 );
81
82 // Tags: user tag + internal Alchemy branding.
83 const tags = yield* appsignals
84 .listTagsForResource({ ResourceArn: probe.arn })
85 .pipe(
86 Effect.map((r) =>
87 Object.fromEntries((r.Tags ?? []).map((t) => [t.Key, t.Value])),
88 ),
89 );
90 expect(tags.fixture).toBe("application-signals-ic");
91 expect(tags["alchemy::id"]).toBe("Probe");
92
93 // UPDATE IN PLACE — configurations are immutable, but tags are not.
94 const { probe: retagged } = yield* stack.deploy(
95 Effect.gen(function* () {
96 const probe = yield* InstrumentationConfiguration("Probe", {
97 ...props,
98 location: location(10),
99 description: "alchemy instrumentation configuration test",
100 tags: { fixture: "application-signals-ic", updated: "true" },
101 });
102 return { probe };
103 }),
104 );
105 expect(retagged.arn).toBe(probe.arn);
106 const updatedTags = yield* appsignals
107 .listTagsForResource({ ResourceArn: probe.arn })
108 .pipe(
109 Effect.map((r) =>
110 Object.fromEntries((r.Tags ?? []).map((t) => [t.Key, t.Value])),
111 ),
112 );
113 expect(updatedTags.updated).toBe("true");
114
115 // REPLACE — changing the code location replaces the configuration.
116 const { probe: replaced } = yield* stack.deploy(
117 Effect.gen(function* () {
118 const probe = yield* InstrumentationConfiguration("Probe", {
119 ...props,
120 location: location(20),
121 description: "alchemy instrumentation configuration test",
122 tags: { fixture: "application-signals-ic" },
123 });
124 return { probe };
125 }),
126 );
127 expect(replaced.locationHash).not.toBe(probe.locationHash);
128 yield* assertGone(probe.locationHash);
129
130 // DESTROY
131 yield* stack.destroy();
132 yield* assertGone(replaced.locationHash);
133 }),
134 {
135 tags: ["provider:aws", "provider:aws:applicationsignals", "live"],
136 timeout: 120_000,
137 },
138);