1import { adopt, OwnedBySomeoneElse } from "@/AdoptPolicy.ts";
2import * as Docker from "@/Docker";
3import * as Provider from "@/Provider";
4import { inMemoryState } from "@/State";
5import * as Test from "@/Test/Alchemy";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8
9const { test } = Test.make({
10 providers: Docker.providers(),
11 state: inMemoryState(),
12});
13
14test.provider(
15 "diff replaces a volume when labels change",
16 () =>
17 Effect.gen(function* () {
18 const volumeProvider = yield* Provider.findProvider(Docker.Volume);
19 const volumeDiff = yield* volumeProvider.diff!({
20 id: "data",
21 fqn: "data",
22 instanceId: "instance",
23 olds: { name: "data", labels: { usage: "old" } },
24 news: { name: "data", labels: { usage: "new" } },
25 oldBindings: [],
26 newBindings: [],
27 output: {
28 id: "data",
29 name: "data",
30 driver: "local",
31 driverOpts: {},
32 labels: { usage: "old" },
33 mountpoint: undefined,
34 createdAt: 0,
35 },
36 });
37 expect(volumeDiff).toEqual({ action: "replace", deleteFirst: true });
38 }),
39 { tags: ["provider:docker", "provider:docker:volume", "local"] },
40);
41
42test.provider(
43 "diff replaces a volume when its Docker context changes",
44 () =>
45 Effect.gen(function* () {
46 const volumeProvider = yield* Provider.findProvider(Docker.Volume);
47 const volumeDiff = yield* volumeProvider.diff!({
48 id: "data",
49 fqn: "data",
50 instanceId: "instance",
51 olds: {
52 name: "data",
53 context: "default",
54 },
55 news: {
56 name: "data",
57 context: "remote-build",
58 },
59 oldBindings: [],
60 newBindings: [],
61 output: {
62 id: "data",
63 name: "data",
64 driver: "local",
65 driverOpts: {},
66 labels: {},
67 mountpoint: undefined,
68 createdAt: 0,
69 },
70 });
71 expect(volumeDiff).toEqual({ action: "replace", deleteFirst: true });
72 }),
73 { tags: ["provider:docker", "provider:docker:volume", "local"] },
74);
75
76describe(
77 "Docker.Volume",
78 {
79 tags: ["provider:docker", "provider:docker:volume", "local"],
80 concurrent: false,
81 },
82 () => {
83 test.provider("creates a volume with labels", (stack) =>
84 Effect.gen(function* () {
85 const docker = yield* Docker.Docker;
86 const volumeName = "alchemy-test-volume-create";
87 yield* Effect.addFinalizer(() =>
88 docker.volume.remove(volumeName).pipe(Effect.ignore),
89 );
90 const volume = yield* stack.deploy(
91 Docker.Volume("created-volume", {
92 name: volumeName,
93 labels: { "com.alchemy.test": "true" },
94 }),
95 );
96 expect(volume.name).toBe(volumeName);
97 expect(volume.id).toBe(volumeName);
98 expect(volume.driver).toBe("local");
99 expect(volume.labels["com.alchemy.test"]).toBe("true");
100 expect(volume.mountpoint?.length).toBeGreaterThan(0);
101 }),
102 );
103
104 test.provider("adopts an existing Docker volume", (stack) =>
105 Effect.gen(function* () {
106 const docker = yield* Docker.Docker;
107 const volumeName = "alchemy-test-volume-adopt-existing";
108 yield* Effect.addFinalizer(() =>
109 docker.volume.remove(volumeName).pipe(Effect.ignore),
110 );
111 yield* docker.volume
112 .remove(volumeName)
113 .pipe(
114 Effect.catchReason("PlatformError", "NotFound", () => Effect.void),
115 );
116 yield* docker.volume.create({ name: volumeName });
117
118 const error = yield* stack
119 .deploy(Docker.Volume("existing-volume", { name: volumeName }))
120 .pipe(Effect.flip);
121 expect(error).toBeInstanceOf(OwnedBySomeoneElse);
122 const volume = yield* stack.deploy(
123 Docker.Volume("existing-volume", { name: volumeName }).pipe(
124 adopt(true),
125 ),
126 );
127 expect(volume.name).toBe(volumeName);
128 expect(volume.id).toBe(volumeName);
129 expect(volume.driver).toBe("local");
130 }),
131 );
132
133 test.provider("replaces a volume when its labels change", (stack) =>
134 Effect.gen(function* () {
135 const docker = yield* Docker.Docker;
136 const volumeName = "alchemy-test-volume-replace";
137 yield* docker.volume
138 .remove(volumeName)
139 .pipe(
140 Effect.catchReason("PlatformError", "NotFound", () => Effect.void),
141 );
142 const first = yield* stack.deploy(
143 Docker.Volume("replaceable-volume", {
144 labels: { generation: "1" },
145 }),
146 );
147 const second = yield* stack.deploy(
148 Docker.Volume("replaceable-volume", {
149 labels: { generation: "2" },
150 }),
151 );
152 expect(second.id).not.toBe(first.id);
153 expect(second.labels.generation).toBe("2");
154 }),
155 );
156 },
157);