1import * as AWS from "@/AWS";
2import { Recipe } from "@/AWS/DataBrew";
3import * as Test from "@/Test/Alchemy";
4import * as databrew from "@distilled.cloud/aws/databrew";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10const getWorkingRecipe = (name: string) =>
11 databrew
12 .describeRecipe({ Name: name, RecipeVersion: "LATEST_WORKING" })
13 .pipe(
14 Effect.catchTag("ResourceNotFoundException", () =>
15 Effect.succeed(undefined),
16 ),
17 );
18
19test.provider(
20 "create, publish, update, delete DataBrew recipe",
21 (stack) =>
22 Effect.gen(function* () {
23 yield* stack.destroy();
24
25 const created = yield* stack.deploy(
26 Effect.gen(function* () {
27 const recipe = yield* Recipe("Clean", {
28 description: "normalize names",
29 publish: true,
30 steps: [
31 {
32 action: {
33 operation: "UPPER_CASE",
34 parameters: { sourceColumn: "name" },
35 },
36 },
37 ],
38 tags: { Environment: "test" },
39 });
40 return { recipe };
41 }),
42 );
43
44 expect(created.recipe.recipeName).toBeDefined();
45 expect(created.recipe.recipeArn).toContain(
46 `:recipe/${created.recipe.recipeName}`,
47 );
48 // publish: true snapshots version 1.0 on create
49 expect(created.recipe.recipeVersion).toEqual("1.0");
50
51 // out-of-band verification of the working copy
52 const observed = yield* getWorkingRecipe(created.recipe.recipeName);
53 expect(observed?.Description).toEqual("normalize names");
54 expect(observed?.Steps?.length).toEqual(1);
55 expect(observed?.Steps?.[0]?.Action.Operation).toEqual("UPPER_CASE");
56 expect(observed?.Tags?.["alchemy::id"]).toBeDefined();
57
58 // idempotent re-deploy: no step change -> no new published version
59 const same = yield* stack.deploy(
60 Effect.gen(function* () {
61 const recipe = yield* Recipe("Clean", {
62 description: "normalize names",
63 publish: true,
64 steps: [
65 {
66 action: {
67 operation: "UPPER_CASE",
68 parameters: { sourceColumn: "name" },
69 },
70 },
71 ],
72 tags: { Environment: "test" },
73 });
74 return { recipe };
75 }),
76 );
77 expect(same.recipe.recipeVersion).toEqual("1.0");
78
79 // update the steps -> working copy changes -> version 2.0 published
80 const updated = yield* stack.deploy(
81 Effect.gen(function* () {
82 const recipe = yield* Recipe("Clean", {
83 description: "normalize names",
84 publish: true,
85 steps: [
86 {
87 action: {
88 operation: "UPPER_CASE",
89 parameters: { sourceColumn: "name" },
90 },
91 },
92 {
93 action: {
94 operation: "REMOVE_VALUES",
95 parameters: { sourceColumn: "email" },
96 },
97 conditionExpressions: [
98 { condition: "IS_MISSING", targetColumn: "email" },
99 ],
100 },
101 ],
102 tags: { Environment: "test" },
103 });
104 return { recipe };
105 }),
106 );
107 expect(updated.recipe.recipeVersion).toEqual("2.0");
108
109 const reobserved = yield* getWorkingRecipe(created.recipe.recipeName);
110 expect(reobserved?.Steps?.length).toEqual(2);
111 expect(reobserved?.Steps?.[1]?.Action.Operation).toEqual("REMOVE_VALUES");
112 expect(
113 reobserved?.Steps?.[1]?.ConditionExpressions?.[0]?.Condition,
114 ).toEqual("IS_MISSING");
115
116 // destroy deletes every version (published + working)
117 yield* stack.destroy();
118 const gone = yield* getWorkingRecipe(created.recipe.recipeName);
119 expect(gone).toBeUndefined();
120 }),
121 { tags: ["provider:aws", "provider:aws:databrew", "live"], timeout: 120_000 },
122);