@@ -159,6 +159,79 @@ describe('EC2 demo buildUserData', () => {
159159 assert . ok ( decoded . includes ( 'docker-compose' ) ) ;
160160 assert . ok ( decoded . includes ( 'docker-buildx' ) ) ;
161161 } ) ;
162+
163+ it ( 'pins the stack clone to a ref instead of tracking main HEAD' , ( ) => {
164+ const decoded = Buffer . from ( _buildUserData ( cfg ) , 'base64' ) . toString ( ) ;
165+ assert . ok ( decoded . includes ( '--branch "$OBS_STACK_REF"' ) ) ;
166+ assert . ok ( decoded . includes ( 'cli-installer-v' ) ) ;
167+ } ) ;
168+
169+ it ( 'clears COMPOSE_PROFILES so local-backend services are pruned in managed mode' , ( ) => {
170+ const decoded = Buffer . from ( _buildUserData ( cfg ) , 'base64' ) . toString ( ) ;
171+ assert . ok ( decoded . includes ( 'export COMPOSE_PROFILES=' ) ) ;
172+ } ) ;
173+ } ) ;
174+
175+ // ββ managed-mode compose project resolution ββββββββββββββββββββββββββββββββββ
176+ // Regression guard for #298: the generated docker-compose.managed.yml must
177+ // resolve as a valid project with COMPOSE_PROFILES empty, with every
178+ // local-backend service pruned (managed mode defines no opensearch/prometheus).
179+
180+ import { execFileSync } from 'node:child_process' ;
181+ import { fileURLToPath } from 'node:url' ;
182+ import { writeFileSync , rmSync } from 'node:fs' ;
183+ import { join , dirname } from 'node:path' ;
184+
185+ function dockerComposeAvailable ( ) {
186+ try {
187+ execFileSync ( 'docker' , [ 'compose' , 'version' ] , { stdio : 'ignore' } ) ;
188+ return true ;
189+ } catch {
190+ return false ;
191+ }
192+ }
193+
194+ // Extract the docker-compose.managed.yml heredoc body from generated user-data.
195+ function extractManagedCompose ( userDataB64 ) {
196+ const decoded = Buffer . from ( userDataB64 , 'base64' ) . toString ( ) ;
197+ const m = decoded . match ( / d o c k e r - c o m p o s e \. m a n a g e d \. y m l < < .M A N A G E D E O F .\n ( [ \s \S ] * ?) \n M A N A G E D E O F / ) ;
198+ if ( ! m ) throw new Error ( 'managed compose heredoc not found in user-data' ) ;
199+ return m [ 1 ] ;
200+ }
201+
202+ describe ( 'EC2 demo managed compose project' , { skip : ! dockerComposeAvailable ( ) } , ( ) => {
203+ const cfg = {
204+ pipelineName : 'test-pipeline' ,
205+ region : 'us-west-2' ,
206+ ingestEndpoints : [ 'test-pipeline-abc123.us-west-2.osis.amazonaws.com' ] ,
207+ } ;
208+ // Repo root holds the included compose files; managed.yml's relative
209+ // include: paths resolve against the file's own directory.
210+ const repoRoot = join ( dirname ( fileURLToPath ( import . meta. url ) ) , '..' , '..' , '..' ) ;
211+ const managedPath = join ( repoRoot , 'docker-compose.managed.yml' ) ;
212+
213+ function configServices ( profiles ) {
214+ return execFileSync (
215+ 'docker' , [ 'compose' , '-f' , managedPath , 'config' , '--services' ] ,
216+ { cwd : repoRoot , env : { ...process . env , COMPOSE_PROFILES : profiles } , encoding : 'utf8' } ,
217+ ) . split ( '\n' ) . filter ( Boolean ) ;
218+ }
219+
220+ it ( 'validates and prunes local-backend services when COMPOSE_PROFILES is empty' , ( ) => {
221+ writeFileSync ( managedPath , extractManagedCompose ( _buildUserData ( cfg ) ) ) ;
222+ try {
223+ const services = configServices ( '' ) ;
224+ assert . ok ( services . length > 0 , 'expected the managed project to resolve to a non-empty service list' ) ;
225+ assert . ok ( ! services . includes ( 'example-agent-eval-canary' ) ,
226+ 'eval canary should be pruned in managed mode (no local opensearch)' ) ;
227+ assert . ok ( ! services . includes ( 'otel-demo-alerting-rules-monitors-init' ) ,
228+ 'otel-demo monitors-init should be pruned in managed mode (no local prometheus/opensearch)' ) ;
229+ assert . ok ( services . includes ( 'otel-collector' ) ,
230+ 'otel-collector should always be present' ) ;
231+ } finally {
232+ rmSync ( managedPath , { force : true } ) ;
233+ }
234+ } ) ;
162235} ) ;
163236
164237// ββ renderPipeline tests βββββββββββββββββββββββββββββββββββββββββββββββββββββ
0 commit comments