1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as androidenterprise from "@distilled.cloud/gcp/androidenterprise_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 enterpriseId,
9 logLevel,
10 probeEnterpriseId,
11 runLifecycle,
12} from "./common.ts";
13
14const { test } = Test.make({ providers: GCP.providers() });
15
16const waitUntilGone = (accountId: string, webAppId: string) =>
17 androidenterprise.getWebapps({ enterpriseId: accountId, webAppId }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider.skipIf(!runLifecycle)(
28 "getWebapps on a missing web app fails with a typed tag",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const error = yield* Effect.flip(
34 androidenterprise.getWebapps({
35 enterpriseId: probeEnterpriseId,
36 webAppId: "app:com.google.enterprise.webapp.alchemy-missing",
37 }),
38 );
39 expect(error._tag).toEqual("NotFound");
40
41 yield* stack.destroy();
42 }).pipe(logLevel),
43 {
44 tags: ["provider:gcp", "provider:gcp:androidenterprise", "live"],
45 timeout: 90_000,
46 },
47);
48
49test.provider.skipIf(runLifecycle)(
50 "insertWebapps without the EMM OAuth scope fails with InsufficientAuthenticationScopes",
51 (stack) =>
52 Effect.gen(function* () {
53 yield* stack.destroy();
54
55 const error = yield* Effect.flip(
56 androidenterprise.insertWebapps({
57 enterpriseId: probeEnterpriseId,
58 body: {
59 title: "Alchemy Probe Web App",
60 startUrl: "https://example.com/",
61 displayMode: "standalone",
62 },
63 }),
64 );
65 expect(error._tag).toEqual("InsufficientAuthenticationScopes");
66
67 yield* stack.destroy();
68 }).pipe(logLevel),
69 {
70 tags: ["provider:gcp", "provider:gcp:androidenterprise", "live"],
71 timeout: 90_000,
72 },
73);
74
75test.provider.skipIf(!runLifecycle)(
76 "create, update, and delete a web app",
77 (stack) =>
78 Effect.gen(function* () {
79 yield* stack.destroy();
80
81 const created = yield* stack.deploy(
82 Effect.gen(function* () {
83 return yield* GCP.AndroidEnterprise.Webapp("Portal", {
84 enterpriseId: enterpriseId!,
85 startUrl: "https://example.com/alchemy-portal",
86 title: "Portal",
87 });
88 }),
89 );
90
91 expect(created.webAppId.length).toBeGreaterThan(0);
92 expect(created.enterpriseId).toEqual(enterpriseId);
93 expect(created.title).toEqual("Portal");
94 expect(created.startUrl).toEqual("https://example.com/alchemy-portal");
95
96 const fetched = yield* androidenterprise.getWebapps({
97 enterpriseId: created.enterpriseId,
98 webAppId: created.webAppId,
99 });
100 expect(fetched.webAppId).toEqual(created.webAppId);
101 expect(fetched.title).toEqual("Portal");
102
103 const updated = yield* stack.deploy(
104 Effect.gen(function* () {
105 return yield* GCP.AndroidEnterprise.Webapp("Portal", {
106 enterpriseId: created.enterpriseId,
107 webAppId: created.webAppId,
108 startUrl: "https://example.com/alchemy-portal",
109 title: "Employee portal",
110 });
111 }),
112 );
113
114 expect(updated.webAppId).toEqual(created.webAppId);
115 expect(updated.title).toEqual("Employee portal");
116
117 yield* stack.destroy();
118
119 const gone = yield* waitUntilGone(created.enterpriseId, created.webAppId);
120 expect(gone).toEqual("gone");
121 }).pipe(logLevel),
122 {
123 tags: ["provider:gcp", "provider:gcp:androidenterprise", "live"],
124 timeout: 90_000,
125 },
126);