@@ -2,6 +2,7 @@ import { SourceControlProviderKind } from "@t3tools/contracts";
22import { assert , describe , it } from "@effect/vitest" ;
33import { GitCommandError } from "@t3tools/contracts" ;
44import * as Effect from "effect/Effect" ;
5+ import * as FileSystem from "effect/FileSystem" ;
56import * as Fiber from "effect/Fiber" ;
67import * as Layer from "effect/Layer" ;
78import * as Option from "effect/Option" ;
@@ -18,6 +19,7 @@ import * as GitCafeCredentials from "./GitCafeCredentials.ts";
1819import * as GitCafeSourceControlProvider from "./GitCafeSourceControlProvider.ts" ;
1920
2021const encodeJson = Schema . encodeSync ( Schema . fromJsonString ( Schema . Unknown ) ) ;
22+ const decodeJson = Schema . decodeUnknownSync ( Schema . fromJsonString ( Schema . Unknown ) ) ;
2123
2224const authAnswer = ( input : { stdout ?: unknown ; stderr ?: unknown ; exitCode ?: number } ) => ( {
2325 stdout : input . stdout === undefined ? "" : encodeJson ( input . stdout ) ,
@@ -29,6 +31,36 @@ const failure = (code: string, status: number | null) => ({
2931 error : { code, status, message : `Failure ${ code } ` } ,
3032} ) ;
3133
34+ /** Most calls touch no file; one that does fails the test. */
35+ const NoFiles = FileSystem . layerNoop ( { } ) ;
36+
37+ /** A provider on git.cafe whose checkout has no remote and which may not run `cafe`. */
38+ const layerFor = (
39+ client : HttpClient . HttpClient ,
40+ options : { readonly files ?: Layer . Layer < FileSystem . FileSystem > } = { } ,
41+ ) =>
42+ Layer . merge ( GitCafeApi . layer , options . files ?? NoFiles ) . pipe (
43+ Layer . provideMerge ( GitCafeCredentials . layer ) ,
44+ Layer . provideMerge (
45+ TestSourceControlHost . layer ( {
46+ git : {
47+ resolvePrimaryRemoteName : ( ) =>
48+ Effect . fail (
49+ new GitCommandError ( {
50+ operation : "resolvePrimaryRemoteName" ,
51+ command : "git remote" ,
52+ cwd : "/repo" ,
53+ detail : "No git remote is configured." ,
54+ } ) ,
55+ ) ,
56+ } ,
57+ } ) ,
58+ ) ,
59+ Layer . provide ( Layer . succeed ( HostProcess . Environment , { CAFE_TOKEN : "env-token" } ) ) ,
60+ Layer . provide ( Layer . succeed ( HostProcess . WorkingDirectory , "/server" ) ) ,
61+ Layer . provide ( Layer . succeed ( HttpClient . HttpClient , client ) ) ,
62+ ) ;
63+
3264describe ( "GitCafeSourceControlProvider" , ( ) => {
3365 it ( "reads a signed-in account and keeps transient failures apart from sign-out" , ( ) => {
3466 const { parseAuth } = GitCafeSourceControlProvider ;
@@ -94,7 +126,7 @@ describe("GitCafeSourceControlProvider", () => {
94126 ] ) ;
95127 } ) . pipe (
96128 Effect . provide (
97- GitCafeApi . layer . pipe (
129+ Layer . merge ( GitCafeApi . layer , NoFiles ) . pipe (
98130 Layer . provideMerge ( GitCafeCredentials . layer ) ,
99131 Layer . provideMerge ( TestSourceControlHost . layer ( ) ) ,
100132 Layer . provide (
@@ -140,7 +172,7 @@ describe("GitCafeSourceControlProvider", () => {
140172 assert . deepStrictEqual ( urls , [ "https://git.cafe/api/repos/team/project" ] ) ;
141173 } ) . pipe (
142174 Effect . provide (
143- GitCafeApi . layer . pipe (
175+ Layer . merge ( GitCafeApi . layer , NoFiles ) . pipe (
144176 Layer . provideMerge ( GitCafeCredentials . layer ) ,
145177 Layer . provideMerge (
146178 TestSourceControlHost . layer ( {
@@ -222,7 +254,7 @@ describe("GitCafeSourceControlProvider", () => {
222254 assert . deepStrictEqual ( remotes , [ expected ] ) ;
223255 } ) . pipe (
224256 Effect . provide (
225- GitCafeApi . layer . pipe (
257+ Layer . merge ( GitCafeApi . layer , NoFiles ) . pipe (
226258 Layer . provideMerge ( GitCafeCredentials . layer ) ,
227259 Layer . provideMerge (
228260 TestSourceControlHost . layer ( {
@@ -246,22 +278,19 @@ describe("GitCafeSourceControlProvider", () => {
246278 ) ;
247279 } ) ;
248280
249- it . effect ( "waits for a created repository's admission before handing out its URLs" , ( ) => {
250- const urls : Array < string > = [ ] ;
251- const states = [ "running" , "complete" ] ;
281+ it . effect ( "creates a repository over REST and waits for its admission" , ( ) => {
282+ const sent : Array < { method : string ; url : string ; body : unknown } > = [ ] ;
252283 const client = HttpClient . make ( ( request ) => {
253- urls . push ( request . url ) ;
284+ const body =
285+ request . body . _tag === "Uint8Array"
286+ ? decodeJson ( new TextDecoder ( ) . decode ( request . body . body ) )
287+ : undefined ;
288+ sent . push ( { method : request . method , url : request . url , body } ) ;
289+ const state = request . method === "POST" ? "running" : "complete" ;
254290 return Effect . succeed (
255291 HttpClientResponse . fromWeb (
256292 request ,
257- new Response (
258- encodeJson ( {
259- repoId : "repo_1" ,
260- state : states . shift ( ) ,
261- owner : "team" ,
262- name : "project" ,
263- } ) ,
264- ) ,
293+ new Response ( encodeJson ( { repoId : "repo_1" , state, owner : "team" , name : "project" } ) ) ,
265294 ) ,
266295 ) ;
267296 } ) ;
@@ -276,51 +305,76 @@ describe("GitCafeSourceControlProvider", () => {
276305 url : "https://git.cafe/team/project" ,
277306 sshUrl : "ssh@git.cafe:team/project.git" ,
278307 } ) ;
279- assert . deepStrictEqual ( urls , [
280- "https://git.cafe/api/orgs/team/admissions/repo_1" ,
281- "https://git.cafe/api/orgs/team/admissions/repo_1" ,
282- ] ) ;
283- } ) . pipe (
284- Effect . provide (
285- GitCafeApi . layer . pipe (
286- Layer . provideMerge ( GitCafeCredentials . layer ) ,
287- Layer . provideMerge (
288- TestSourceControlHost . layer ( {
289- process : {
290- run : ( ) =>
291- Effect . succeed (
292- TestSourceControlHost . processOutput (
293- encodeJson ( {
294- schemaVersion : 1 ,
295- data : {
296- resource : {
297- repoId : "repo_1" ,
298- state : "running" ,
299- owner : "team" ,
300- name : "project" ,
301- } ,
302- } ,
303- } ) ,
304- ) ,
305- ) ,
306- } ,
307- git : {
308- resolvePrimaryRemoteName : ( ) =>
309- Effect . fail (
310- new GitCommandError ( {
311- operation : "resolvePrimaryRemoteName" ,
312- command : "git remote" ,
313- cwd : "/repo" ,
314- detail : "No git remote is configured." ,
315- } ) ,
316- ) ,
317- } ,
318- } ) ,
308+ assert . deepStrictEqual (
309+ sent . map ( ( request ) => `${ request . method } ${ request . url } ` ) ,
310+ [
311+ "POST https://git.cafe/api/orgs/team/repos" ,
312+ "GET https://git.cafe/api/orgs/team/admissions/repo_1" ,
313+ ] ,
314+ ) ;
315+ const { requestKey, ...body } = sent [ 0 ] ?. body as { requestKey : string } ;
316+ assert . match ( requestKey , / ^ [ 0 - 9 a - f - ] { 36 } $ / u) ;
317+ assert . deepStrictEqual ( body , {
318+ name : "project" ,
319+ visibility : "private" ,
320+ defaultBranch : "main" ,
321+ } ) ;
322+ } ) . pipe ( Effect . provide ( layerFor ( client ) ) ) ;
323+ } ) ;
324+
325+ it . effect ( "opens a pull request from a fork over REST with the body file's text" , ( ) => {
326+ const sent : Array < { method : string ; url : string ; body : unknown } > = [ ] ;
327+ const client = HttpClient . make ( ( request ) => {
328+ const body =
329+ request . body . _tag === "Uint8Array"
330+ ? decodeJson ( new TextDecoder ( ) . decode ( request . body . body ) )
331+ : undefined ;
332+ sent . push ( { method : request . method , url : request . url , body } ) ;
333+ return Effect . succeed (
334+ HttpClientResponse . fromWeb (
335+ request ,
336+ new Response (
337+ encodeJson (
338+ request . method === "POST"
339+ ? { number : 3 }
340+ : { id : "repo_fork" , name : "project" , defaultBranch : "main" } ,
341+ ) ,
319342 ) ,
320- Layer . provide ( Layer . succeed ( HostProcess . Environment , { CAFE_TOKEN : "env-token" } ) ) ,
321- Layer . provide ( Layer . succeed ( HostProcess . WorkingDirectory , "/server" ) ) ,
322- Layer . provide ( Layer . succeed ( HttpClient . HttpClient , client ) ) ,
323343 ) ,
344+ ) ;
345+ } ) ;
346+ return Effect . gen ( function * ( ) {
347+ const provider = yield * GitCafeSourceControlProvider . make ;
348+ yield * provider . createChangeRequest ( {
349+ cwd : "/repo" ,
350+ target : { repository : "team/project" , refName : "main" } ,
351+ baseRefName : "main" ,
352+ headSelector : "fork:feature" ,
353+ title : "Add a thing" ,
354+ bodyFile : "/tmp/body.md" ,
355+ } ) ;
356+ assert . deepStrictEqual (
357+ sent . map ( ( request ) => `${ request . method } ${ request . url } ` ) ,
358+ [
359+ "GET https://git.cafe/api/repos/fork/project" ,
360+ "POST https://git.cafe/api/repos/team/project/pulls" ,
361+ ] ,
362+ ) ;
363+ assert . deepStrictEqual ( sent [ 1 ] ?. body , {
364+ repo : "repo_fork" ,
365+ source : "feature" ,
366+ target : "main" ,
367+ title : "Add a thing" ,
368+ description : "Body" ,
369+ } ) ;
370+ } ) . pipe (
371+ Effect . provide (
372+ layerFor ( client , {
373+ files : FileSystem . layerNoop ( {
374+ readFileString : ( path ) =>
375+ path === "/tmp/body.md" ? Effect . succeed ( "Body" ) : Effect . die ( path ) ,
376+ } ) ,
377+ } ) ,
324378 ) ,
325379 ) ;
326380 } ) ;
0 commit comments