1import * as AWS from "@/AWS";
2import { Domain, Project } from "@/AWS/DataZone";
3import * as Test from "@/Test/Alchemy";
4import * as datazone from "@distilled.cloud/aws/datazone";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Redacted from "effect/Redacted";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11const unredact = (value: string | Redacted.Redacted<string>): string =>
12 Redacted.isRedacted(value) ? Redacted.value(value) : value;
13
14// Once the parent domain is deleted, GetProject is reported as
15// AccessDeniedException rather than ResourceNotFoundException — DataZone
16// checks domain-scoped auth before existence. Both mean "absent".
17const findProject = (domainId: string, projectId: string) =>
18 datazone
19 .getProject({ domainIdentifier: domainId, identifier: projectId })
20 .pipe(
21 Effect.catchTag("ResourceNotFoundException", () =>
22 Effect.succeed(undefined),
23 ),
24 Effect.catchTag("AccessDeniedException", () => Effect.succeed(undefined)),
25 );
26
27test.provider(
28 "create project in a domain, update description, delete",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 // 1. Deploy a domain and a project inside it.
34 const result = yield* stack.deploy(
35 Effect.gen(function* () {
36 const domain = yield* Domain("ProjectTestDomain", {
37 description: "alchemy datazone project test",
38 });
39 const project = yield* Project("TestProject", {
40 domainId: domain.domainId,
41 description: "analytics project",
42 });
43 return {
44 domainId: domain.domainId,
45 projectId: project.projectId,
46 projectName: project.name,
47 projectStatus: project.projectStatus,
48 domainUnitId: project.domainUnitId,
49 };
50 }),
51 );
52
53 expect(result.projectId).toBeDefined();
54 expect(result.projectStatus).toBe("ACTIVE");
55
56 // out-of-band: project exists inside the domain.
57 const created = yield* findProject(result.domainId, result.projectId);
58 expect(created).toBeDefined();
59 expect(unredact(created!.name)).toBe(result.projectName);
60 expect(
61 created!.description === undefined
62 ? undefined
63 : unredact(created!.description),
64 ).toBe("analytics project");
65
66 // 2. Update the description — converges via updateProject, same project.
67 const updated = yield* stack.deploy(
68 Effect.gen(function* () {
69 const domain = yield* Domain("ProjectTestDomain", {
70 description: "alchemy datazone project test",
71 });
72 const project = yield* Project("TestProject", {
73 domainId: domain.domainId,
74 description: "analytics project (updated)",
75 });
76 return { projectId: project.projectId };
77 }),
78 );
79 expect(updated.projectId).toBe(result.projectId);
80
81 const observed = yield* findProject(result.domainId, result.projectId);
82 expect(
83 observed!.description === undefined
84 ? undefined
85 : unredact(observed!.description),
86 ).toBe("analytics project (updated)");
87
88 // 3. Destroy — the project is deleted before its domain.
89 yield* stack.destroy();
90 const gone = yield* findProject(result.domainId, result.projectId);
91 expect(gone === undefined || gone.projectStatus === "DELETING").toBe(
92 true,
93 );
94 }),
95 { tags: ["provider:aws", "provider:aws:datazone", "live"], timeout: 480_000 },
96);