1+ import type { Response } from '@cloudflare/workers-types'
2+ import { describe , it , expect , beforeAll , afterAll } from 'vitest'
3+ import { unstable_dev , UnstableDevWorker } from 'wrangler'
4+ import ezSpawn from "@jsdevtools/ez-spawn"
5+ import pushWorkflowRunInProgressFixture from './fixtures/workflow_run.in_progress.json'
6+ import prWorkflowRunRequestedFixture from './fixtures/pr.workflow_run.requested.json'
7+ import prPullRequestSynchronizeFixture from './fixtures/pr.pull_request.json'
8+ import { setupServer } from 'msw/node'
9+ import { http , HttpResponse } from 'msw'
10+
11+ const server = setupServer (
12+ // Mock the GitHub API installation endpoint
13+ http . get ( 'https://api.github.com/repos/:owner/:repo/installation' , ( ctx ) => {
14+ return HttpResponse . json ( {
15+ id : 1234567 ,
16+ access_tokens_url : 'https://api.github.com/app/installations/1234567/access_tokens' ,
17+ } )
18+ } ) ,
19+
20+ // Mock the GitHub API token endpoint
21+ http . post ( 'https://api.github.com/app/installations/:installation_id/access_tokens' , ( ctx ) => {
22+ return HttpResponse . json ( {
23+ token : 'ghs_mock_token' ,
24+ expires_at : '2024-03-22T00:00:00Z' ,
25+ } )
26+ } )
27+ )
28+
29+ let worker : UnstableDevWorker
30+
31+ beforeAll ( async ( ) => {
32+ // Start MSW server
33+ server . listen ( )
34+
35+ await ezSpawn . async ( 'pnpm cross-env TEST=true pnpm --filter=backend run build' , [ ] , {
36+ stdio : "inherit" ,
37+ shell : true ,
38+ } ) ;
39+ worker = await unstable_dev ( 'dist/_worker.js' , {
40+ config : './wrangler.toml' ,
41+ } )
42+ const url = `${ worker . proxyData . userWorkerUrl . protocol } //${ worker . proxyData . userWorkerUrl . hostname } :${ worker . proxyData . userWorkerUrl . port } `
43+ console . log ( url )
44+ await ezSpawn . async ( `pnpm cross-env TEST=true API_URL=${ url } pnpm --filter=pkg-pr-new run build` , [ ] , {
45+ stdio : "inherit" ,
46+ shell : true ,
47+ } ) ;
48+ } )
49+
50+ afterAll ( ( ) => {
51+ server . close ( )
52+ } )
53+
54+ describe . sequential . each ( [
55+ [ pushWorkflowRunInProgressFixture ] ,
56+ [ prWorkflowRunRequestedFixture , prPullRequestSynchronizeFixture ]
57+ ] as const ) ( 'webhook endpoints' , ( ...fixture ) => {
58+ const [ { event, payload} , pr ] = fixture
59+ const mode = pr ? 'pr' : 'commit'
60+ it ( `handles ${ mode } events` , async ( ) => {
61+ // Send PR event if exists
62+ if ( pr ) {
63+ const prResponse = await worker . fetch ( '/webhook' , {
64+ method : 'POST' ,
65+ headers : {
66+ 'x-github-delivery' : 'd81876a0-e8c4-11ee-8fca-9d3a2baa9707' ,
67+ 'x-github-event' : 'pull_request' ,
68+ } ,
69+ body : JSON . stringify ( pr . payload ) ,
70+ } )
71+ expect ( await prResponse . json ( ) ) . toEqual ( { ok : true } )
72+ }
73+
74+ // Send workflow run event
75+ const response = await worker . fetch ( '/webhook' , {
76+ method : 'POST' ,
77+ headers : {
78+ 'x-github-delivery' : 'd81876a0-e8c4-11ee-8fca-9d3a2baa9707' ,
79+ 'x-github-event' : event ,
80+ } ,
81+ body : JSON . stringify ( payload ) ,
82+ } )
83+ expect ( await response . json ( ) ) . toEqual ( { ok : true } )
84+ } )
85+
86+ it ( `publishes playgrounds for ${ mode } ` , async ( ) => {
87+ const env = Object . entries ( {
88+ TEST : true ,
89+ GITHUB_SERVER_URL : new URL ( payload . workflow_run . html_url ) . origin ,
90+ GITHUB_REPOSITORY : payload . repository . full_name ,
91+ GITHUB_RUN_ID : payload . workflow_run . id ,
92+ GITHUB_RUN_ATTEMPT : payload . workflow_run . run_attempt ,
93+ GITHUB_ACTOR_ID : payload . sender . id ,
94+ GITHUB_SHA : payload . workflow_run . head_sha ,
95+ GITHUB_ACTION : payload . workflow_run . id ,
96+ GITHUB_JOB : payload . workflow_run . name ,
97+ GITHUB_REF_NAME : pr
98+ ? `${ pr . payload . number } /merge`
99+ : payload . workflow_run . head_branch ,
100+ } )
101+ . map ( ( [ k , v ] ) => `${ k } =${ JSON . stringify ( v ) } ` )
102+ . join ( " " ) ;
103+
104+ try {
105+ const process = await ezSpawn . async ( `pnpm cross-env ${ env } pnpm run -w publish:playgrounds` , [ ] , {
106+ stdio : "overlapped" ,
107+ shell : true ,
108+ } ) ;
109+ console . log ( process . stdout )
110+
111+ } catch ( e ) {
112+ console . log ( e )
113+
114+ }
115+
116+ } )
117+
118+ it ( `serves and installs playground-a for ${ mode } ` , async ( ) => {
119+ const [ owner , repo ] = payload . repository . full_name . split ( '/' )
120+ const sha = payload . workflow_run . head_sha . substring ( 0 , 7 )
121+ const ref = pr ?. payload . number ?? payload . workflow_run . head_branch
122+
123+ // Test download with SHA
124+ const shaResponse = await worker . fetch ( `/${ owner } /${ repo } /playground-a@${ sha } ` )
125+ expect ( shaResponse . status ) . toBe ( 200 )
126+ const shaBlob = await shaResponse . blob ( )
127+ expect ( shaBlob . size ) . toBeGreaterThan ( 0 )
128+
129+ // Test download with ref matches SHA content
130+ const refResponse = await fetchWithRedirect ( `/${ owner } /${ repo } /playground-a@${ ref } ` )
131+ const refBlob = await refResponse . blob ( )
132+ const shaBlobSize = await shaBlob . arrayBuffer ( ) ;
133+ const refBlobSize = await refBlob . arrayBuffer ( ) ;
134+ expect ( shaBlobSize . byteLength ) . toEqual ( refBlobSize . byteLength ) ;
135+ expect ( shaBlobSize ) . toEqual ( refBlobSize ) ;
136+
137+ // Test installation
138+ const url = `/${ owner } /${ repo } /playground-a@${ sha } ?id=${ Date . now ( ) } `
139+ const installProcess = await ezSpawn . async ( `pnpm cross-env CI=true npx -f playground-a@${ url } ` , {
140+ stdio : "overlapped" ,
141+ shell : true ,
142+ } ) ;
143+ expect ( installProcess . stdout ) . toContain ( 'playground-a installed successfully!' )
144+ } )
145+
146+ it ( `serves and installs playground-b for ${ mode } ` , async ( ) => {
147+ const [ owner , repo ] = payload . repository . full_name . split ( '/' )
148+ const sha = payload . workflow_run . head_sha . substring ( 0 , 7 )
149+
150+ // Test download
151+ const response = await worker . fetch ( `/${ owner } /${ repo } /playground-b@${ sha } ` )
152+ expect ( response . status ) . toBe ( 200 )
153+
154+ // Test installation
155+ const url = `/${ owner } /${ repo } /playground-b@${ sha } ?id=${ Date . now ( ) } `
156+ const installProcess = await ezSpawn . async ( `pnpm cross-env CI=true npx -f playground-b@${ url } ` , {
157+ stdio : "overlapped" ,
158+ shell : true ,
159+ } ) ;
160+ expect ( installProcess . stdout ) . toContain ( 'playground-a installed successfully!' ) // Should import playground-a
161+ expect ( installProcess . stdout ) . toContain ( 'playground-b installed successfully!' )
162+ } )
163+ } )
164+
165+ describe ( 'URL redirects' , ( ) => {
166+ describe ( 'standard packages' , ( ) => {
167+ it ( 'redirects full URLs correctly' , async ( ) => {
168+ const response = await fetchWithRedirect ( '/tinylibs/tinybench@a832a55' )
169+ expect ( response . url ) . toContain ( '/tinylibs/tinybench/tinybench@a832a55' )
170+ } )
171+
172+ it ( 'redirects compact URLs correctly' , async ( ) => {
173+ const response = await fetchWithRedirect ( '/tinybench@a832a55' )
174+ expect ( response . url ) . toContain ( '/tinylibs/tinybench/tinybench@a832a55' )
175+ } )
176+ } )
177+
178+ describe ( 'scoped packages' , ( ) => {
179+ const expectedPath = `/stackblitz/sdk/${ encodeURIComponent ( '@stackblitz/sdk' ) } @a832a55`
180+
181+ it ( 'redirects full scoped package URLs correctly' , async ( ) => {
182+ const response = await fetchWithRedirect ( '/stackblitz/sdk/@stackblitz/sdk@a832a55' )
183+ expect ( response . url ) . toContain ( expectedPath )
184+ } )
185+
186+ it ( 'redirects compact scoped package URLs correctly' , async ( ) => {
187+ const response = await fetchWithRedirect ( '/@stackblitz/sdk@a832a55' )
188+ expect ( response . url ) . toContain ( expectedPath )
189+ } )
190+ } )
191+ } )
192+
193+ async function fetchWithRedirect ( url : string , maxRedirects = 999 ) : Promise < Response > {
194+ const response = await worker . fetch ( url , { redirect : 'manual' } )
195+
196+ if ( response . status >= 300 && response . status < 400 && maxRedirects > 0 ) {
197+ const location = response . headers . get ( 'location' )
198+ if ( location ) {
199+ return fetchWithRedirect ( location , maxRedirects - 1 )
200+ }
201+ }
202+
203+ return response
204+ }
0 commit comments