1import { SourceControlProviderKind } from "@t3tools/contracts";
2import { assert, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Option from "effect/Option";
6
7import * as AzureDevOpsCli from "./AzureDevOpsCli.ts";
8import * as AzureDevOpsSourceControlProvider from "./AzureDevOpsSourceControlProvider.ts";
9
10function makeProvider(azure: Partial<AzureDevOpsCli.AzureDevOpsCli["Service"]>) {
11 return AzureDevOpsSourceControlProvider.make.pipe(
12 Effect.provide(Layer.mock(AzureDevOpsCli.AzureDevOpsCli)(azure)),
13 );
14}
15
16it.effect("maps Azure DevOps PR summaries into provider-neutral change requests", () =>
17 Effect.gen(function* () {
18 const provider = yield* makeProvider({
19 getPullRequest: () =>
20 Effect.succeed({
21 number: 42,
22 title: "Add Azure provider",
23 url: "https://dev.azure.com/acme/project/_git/repo/pullrequest/42",
24 baseRefName: "main",
25 headRefName: "feature/source-control",
26 state: "closed",
27 isCrossRepository: false,
28 closedAt: "2026-08-23T10:00:00Z",
29 updatedAt: Option.none(),
30 }),
31 });
32
33 const changeRequest = yield* provider.getChangeRequest({
34 cwd: "/repo",
35 reference: "42",
36 });
37
38 assert.deepStrictEqual(changeRequest, {
39 provider: SourceControlProviderKind.make("azure-devops"),
40 number: 42,
41 title: "Add Azure provider",
42 url: "https://dev.azure.com/acme/project/_git/repo/pullrequest/42",
43 baseRefName: "main",
44 headRefName: "feature/source-control",
45 state: "closed",
46 closedAt: "2026-08-23T10:00:00Z",
47 mergedAt: null,
48 updatedAt: Option.none(),
49 isCrossRepository: false,
50 });
51 }),
52);
53
54it.effect("adds change-request context while retaining Azure CLI causes", () =>
55 Effect.gen(function* () {
56 const cause = new AzureDevOpsCli.AzureDevOpsCommandFailedError({
57 operation: "execute",
58 command: "az",
59 cwd: "/repo",
60 argumentCount: 2,
61 cause: new Error("raw upstream detail that should remain in the cause"),
62 });
63 const provider = yield* makeProvider({
64 checkoutPullRequest: () => Effect.fail(cause),
65 });
66
67 const error = yield* provider
68 .checkoutChangeRequest({ cwd: "/repo", reference: "#42" })
69 .pipe(Effect.flip);
70
71 assert.deepStrictEqual(
72 {
73 provider: error.provider,
74 operation: error.operation,
75 command: error.command,
76 cwd: error.cwd,
77 reference: error.reference,
78 detail: error.detail,
79 },
80 {
81 provider: "azure-devops",
82 operation: "checkoutChangeRequest",
83 command: "az",
84 cwd: "/repo",
85 reference: "#42",
86 detail: "Azure DevOps CLI command failed.",
87 },
88 );
89 assert.strictEqual(error.cause, cause);
90 assert.equal(error.message.includes("raw upstream detail"), false);
91 }),
92);
93
94it.effect("creates Azure DevOps PRs through provider-neutral input names", () =>
95 Effect.gen(function* () {
96 let createInput:
97 | Parameters<AzureDevOpsCli.AzureDevOpsCli["Service"]["createPullRequest"]>[0]
98 | null = null;
99 const provider = yield* makeProvider({
100 createPullRequest: (input) => {
101 createInput = input;
102 return Effect.void;
103 },
104 });
105
106 yield* provider.createChangeRequest({
107 cwd: "/repo",
108 baseRefName: "main",
109 headSelector: "feature/provider",
110 title: "Provider PR",
111 bodyFile: "/tmp/body.md",
112 });
113
114 assert.deepStrictEqual(createInput, {
115 cwd: "/repo",
116 baseBranch: "main",
117 headSelector: "feature/provider",
118 title: "Provider PR",
119 bodyFile: "/tmp/body.md",
120 });
121 }),
122);