33 *
44 * Tests the full provisioning flow end-to-end:
55 * 1. Calls provision() to create K8s namespaces, secrets, and Knative Services
6- * 2. Verifies Knative Service reaches Ready state
7- * 3. Calls the Knative Service via Kourier gateway
8- * 4. Verifies the service_url was written back to the DB
6+ * 2. Verifies K8s namespace + secret were created correctly
7+ * 3. Verifies Knative Service reaches Ready state
8+ * 4. Calls the function via in-cluster curl (kubectl run)
9+ * 5. Verifies the service_url was written back to the DB
10+ * 6. Re-runs seed to verify idempotency
911 *
1012 * Requires:
1113 * - K8S_API_URL pointing to kubectl proxy (e.g. http://localhost:8001)
1214 * - PGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE for a bootstrapped DB
1315 * - Knative Serving + Kourier installed in the kind cluster
14- * - KOURIER_URL for the Kourier gateway (e.g. http://localhost:8090)
1516 *
1617 * DB must be bootstrapped with:
1718 * tests/e2e/fixtures/provisioning-knative-bootstrap.sql
1819 */
1920
21+ import { execSync } from 'child_process' ;
22+
2023import { provision } from '@constructive-io/provisioning-handlers' ;
2124import { InterwebClient } from '@kubernetesjs/ops' ;
2225import pg from 'pg' ;
2326
2427const { Pool } = pg ;
2528
2629const K8S_API_URL = process . env . K8S_API_URL ;
27- const KOURIER_URL = process . env . KOURIER_URL || 'http://localhost:8090' ;
2830const DATABASE_ID = '00000000-0000-0000-0000-000000000001' ;
2931
3032const describeKnative = K8S_API_URL ? describe : describe . skip ;
@@ -164,56 +166,62 @@ describeKnative('Provisioning E2E — Knative in kind', () => {
164166 expect ( ready ) . toBe ( true ) ;
165167 } , 360_000 ) ;
166168
167- // ─── Phase 4: Call the Knative Service ────────────────── ─────────────────
169+ // ─── Phase 4: Call the function from inside the cluster ─────────────────
168170
169- it ( 'responds to HTTP requests through Kourier gateway ' , async ( ) => {
170- // Get the ksvc URL to determine the Host header
171+ it ( 'responds to HTTP requests (in-cluster curl) ' , async ( ) => {
172+ // Get the ksvc URL from the K8s API
171173 const resp = await fetch (
172174 `${ K8S_API_URL } /apis/serving.knative.dev/v1/namespaces/test-ns/services/hello-provisioned`
173175 ) ;
174176 expect ( resp . ok ) . toBe ( true ) ;
175177 const svc = ( await resp . json ( ) ) as Record < string , any > ;
176178 const ksvcUrl = ( svc ?. status ?. url || '' ) as string ;
179+ console . log ( `ksvc URL: ${ ksvcUrl } ` ) ;
177180
178- // Extract hostname from the ksvc URL
179- let hostname : string ;
180- try {
181- hostname = new URL ( ksvcUrl ) . hostname ;
182- } catch {
183- // Fallback: construct the expected hostname
184- hostname = 'hello-provisioned.test-ns.svc.cluster.local' ;
185- }
186- console . log ( `Calling ksvc via Kourier: Host=${ hostname } , URL=${ KOURIER_URL } ` ) ;
187-
188- // Call through Kourier with Host header
181+ // Curl from inside the cluster using kubectl run.
182+ // The Knative Service has an in-cluster K8s Service that resolves via DNS.
183+ const inClusterUrl = `http://hello-provisioned.test-ns.svc.cluster.local` ;
189184 let body = '' ;
190185 let success = false ;
191- const maxRetries = 10 ;
186+ const maxRetries = 12 ;
187+ const suffix = Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) ;
192188
193189 for ( let i = 0 ; i < maxRetries ; i ++ ) {
194190 try {
195- const fnResp = await fetch ( KOURIER_URL , {
196- headers : { Host : hostname } ,
197- } ) ;
198- body = await fnResp . text ( ) ;
199- console . log ( ` attempt ${ i + 1 } : status=${ fnResp . status } body="${ body . slice ( 0 , 200 ) } "` ) ;
200-
201- if ( fnResp . ok ) {
191+ const podName = `curl-e2e-${ suffix } -${ i } ` ;
192+ body = execSync (
193+ `kubectl run ${ podName } --rm -i --restart=Never ` +
194+ `--image=curlimages/curl --request-timeout=60s -- ` +
195+ `curl -s --max-time 30 ${ inClusterUrl } ` ,
196+ { encoding : 'utf-8' , timeout : 120_000 }
197+ ) . trim ( ) ;
198+
199+ console . log ( ` attempt ${ i + 1 } : body="${ body . slice ( 0 , 200 ) } "` ) ;
200+ if ( body . includes ( 'Hello' ) ) {
202201 success = true ;
203202 break ;
204203 }
205- } catch ( err ) {
206- console . log ( ` attempt ${ i + 1 } : fetch error — ${ err } ` ) ;
204+ } catch ( err : unknown ) {
205+ const msg = err instanceof Error ? err . message : String ( err ) ;
206+ // Extract stdout from the error if available
207+ const errWithOutput = err as { stdout ?: string ; stderr ?: string } ;
208+ if ( errWithOutput . stdout ) {
209+ body = errWithOutput . stdout . trim ( ) ;
210+ if ( body . includes ( 'Hello' ) ) {
211+ console . log ( ` attempt ${ i + 1 } : got body from stdout="${ body . slice ( 0 , 200 ) } "` ) ;
212+ success = true ;
213+ break ;
214+ }
215+ }
216+ console . log ( ` attempt ${ i + 1 } : error — ${ msg . slice ( 0 , 200 ) } ` ) ;
207217 }
208- await sleep ( 5_000 ) ;
218+ await sleep ( 10_000 ) ;
209219 }
210220
211221 expect ( success ) . toBe ( true ) ;
212- // The helloworld-go container reads TARGET from env (mounted via K8s secret)
213- // and returns "Hello {TARGET}!"
214222 expect ( body ) . toContain ( 'Hello' ) ;
215223 expect ( body ) . toContain ( 'Provisioning E2E' ) ;
216- } , 120_000 ) ;
224+ } , 240_000 ) ;
217225
218226 // ─── Phase 5: Verify DB writeback ────────────────────────────────────────
219227
@@ -224,8 +232,6 @@ describeKnative('Provisioning E2E — Knative in kind', () => {
224232 ) ;
225233
226234 expect ( rows ) . toHaveLength ( 1 ) ;
227- // service_url may or may not be set depending on whether the create response
228- // included it. At minimum, we verify the row still exists.
229235 console . log ( `service_url in DB: ${ rows [ 0 ] . service_url || '(not set yet)' } ` ) ;
230236 } ) ;
231237
0 commit comments