1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as androidmanagement from "@distilled.cloud/gcp/androidmanagement_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { enterpriseName, logLevel, runLifecycle } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 androidmanagement.getEnterprisesEnrollmentTokens({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.repeat({
16 schedule: Schedule.spaced("1 second"),
17 until: (status) => status === "gone",
18 times: 10,
19 }),
20 );
21
22test.provider.skipIf(runLifecycle)(
23 "getEnterprisesEnrollmentTokens without the Android Management scope fails with InsufficientScopes",
24 (stack) =>
25 Effect.gen(function* () {
26 yield* stack.destroy();
27
28 const error = yield* Effect.flip(
29 androidmanagement.getEnterprisesEnrollmentTokens({
30 name: "enterprises/alchemy-missing-enterprise/enrollmentTokens/alchemy-missing",
31 }),
32 );
33 expect(error._tag).toEqual("InsufficientScopes");
34
35 yield* stack.destroy();
36 }).pipe(logLevel),
37 {
38 tags: ["provider:gcp", "provider:gcp:androidmanagement", "live"],
39 timeout: 90_000,
40 },
41);
42
43test.provider.skipIf(runLifecycle)(
44 "createEnterprisesEnrollmentTokens without Android Management access fails with a typed entitlement error",
45 (stack) =>
46 Effect.gen(function* () {
47 yield* stack.destroy();
48
49 const error = yield* Effect.flip(
50 androidmanagement.createEnterprisesEnrollmentTokens({
51 parent: "enterprises/alchemy-missing-enterprise",
52 body: {
53 duration: "3600s",
54 additionalData: "alchemy-probe",
55 },
56 }),
57 );
58 expect(error._tag).toEqual("InsufficientScopes");
59
60 yield* stack.destroy();
61 }).pipe(logLevel),
62 {
63 tags: ["provider:gcp", "provider:gcp:androidmanagement", "live"],
64 timeout: 90_000,
65 },
66);
67
68test.provider.skipIf(!runLifecycle)(
69 "create, update, and delete an enrollment token",
70 (stack) =>
71 Effect.gen(function* () {
72 yield* stack.destroy();
73
74 const created = yield* stack.deploy(
75 Effect.gen(function* () {
76 const enterprise = enterpriseName
77 ? { name: enterpriseName }
78 : yield* GCP.AndroidManagement.Enterprise("TokenHost", {
79 enterpriseDisplayName: "Token Host",
80 });
81 const token = yield* GCP.AndroidManagement.EnterprisesEnrollmentToken(
82 "Enroll",
83 {
84 parent: enterprise.name,
85 duration: "86400s",
86 additionalData: "org-unit-a",
87 },
88 );
89 return { enterprise, token };
90 }),
91 );
92
93 expect(created.token.name).toContain("/enrollmentTokens/");
94 expect(created.token.parent).toEqual(created.enterprise.name);
95 expect(created.token.enrollmentTokenId.length).toBeGreaterThan(0);
96 expect(created.token.value).toEqual(expect.any(String));
97
98 const fetched = yield* androidmanagement.getEnterprisesEnrollmentTokens({
99 name: created.token.name,
100 });
101 expect(fetched.name).toEqual(created.token.name);
102
103 const updated = yield* stack.deploy(
104 Effect.gen(function* () {
105 const enterprise = enterpriseName
106 ? { name: enterpriseName }
107 : yield* GCP.AndroidManagement.Enterprise("TokenHost", {
108 enterpriseDisplayName: "Token Host",
109 });
110 return yield* GCP.AndroidManagement.EnterprisesEnrollmentToken(
111 "Enroll",
112 {
113 parent: enterprise.name,
114 duration: "172800s",
115 additionalData: "org-unit-b",
116 },
117 );
118 }),
119 );
120
121 expect(updated.name).not.toEqual(created.token.name);
122 expect(updated.parent).toEqual(created.enterprise.name);
123
124 yield* stack.destroy();
125
126 const gone = yield* waitUntilGone(updated.name);
127 expect(gone).toEqual("gone");
128 }).pipe(logLevel),
129 {
130 tags: ["provider:gcp", "provider:gcp:androidmanagement", "live"],
131 timeout: 90_000,
132 },
133);