@@ -133,18 +133,15 @@ func TestMultiPlayPlayFilterComposesWithTags(t *testing.T) {
133133 tasks:
134134 - name: deploy-api
135135 tags: [deploy]
136- dokku_app:
137- app: docket-test-deploy-api
136+ dokku_stub: { key: deploy-api }
138137 - name: configure-api
139138 tags: [configure]
140- dokku_app:
141- app: docket-test-configure-api
139+ dokku_stub: { key: configure-api }
142140- name: worker
143141 tasks:
144142 - name: deploy-worker
145143 tags: [deploy]
146- dokku_app:
147- app: docket-test-deploy-worker
144+ dokku_stub: { key: deploy-worker }
148145` )
149146 stdout , _ , _ := runPlan (t , path , "--play" , "api" , "--tags" , "deploy" )
150147 if ! strings .Contains (stdout , "deploy-api" ) {
@@ -168,14 +165,12 @@ func TestMultiPlayPlayLevelTagsPropagateToTasks(t *testing.T) {
168165 tags: [api]
169166 tasks:
170167 - name: deploy-api
171- dokku_app:
172- app: docket-test-deploy-api
168+ dokku_stub: { key: deploy-api }
173169- name: worker
174170 tags: [worker]
175171 tasks:
176172 - name: deploy-worker
177- dokku_app:
178- app: docket-test-deploy-worker
173+ dokku_stub: { key: deploy-worker }
179174` )
180175 stdout , _ , _ := runPlan (t , path , "--tags" , "api" )
181176 if ! strings .Contains (stdout , "deploy-api" ) {
@@ -238,6 +233,41 @@ func TestPlanFailedWhenClearsError(t *testing.T) {
238233 }
239234}
240235
236+ // TestPlanProbeErrorRendersMarkerAndExits pins the documented contract
237+ // that an uncleared probe error renders [!] on stderr and makes plan
238+ // exit 1, and that errors win over drift under --detailed-exitcode (exit
239+ // 1, not 2). This is the end-to-end guard for #328: a probe that could
240+ // not run must not be reported as absent with an optimistic [+] create.
241+ func TestPlanProbeErrorRendersMarkerAndExits (t * testing.T ) {
242+ defer stubReset ()
243+ stubSet ("a" , StubFixture {
244+ PlanError : errors .New (`exec: "dokku": executable file not found in $PATH` ),
245+ })
246+
247+ path := writeMultiPlayTasks (t , `---
248+ - tasks:
249+ - name: needs-dokku
250+ dokku_stub: { key: a }
251+ ` )
252+
253+ stdout , stderr , exit := runPlan (t , path )
254+ if exit != 1 {
255+ t .Fatalf ("exit = %d, want 1; stdout=%s stderr=%s" , exit , stdout , stderr )
256+ }
257+ if ! strings .Contains (stderr , "[!]" ) {
258+ t .Errorf ("expected [!] marker on stderr; got stdout=%s stderr=%s" , stdout , stderr )
259+ }
260+ if strings .Contains (stdout , "[+]" ) {
261+ t .Errorf ("plan must not predict [+] create for state it never read; got:\n %s" , stdout )
262+ }
263+
264+ // --detailed-exitcode: errors win over drift, so exit stays 1, not 2.
265+ _ , _ , exit = runPlan (t , path , "--detailed-exitcode" )
266+ if exit != 1 {
267+ t .Errorf ("detailed-exitcode exit = %d, want 1 (errors win over drift)" , exit )
268+ }
269+ }
270+
241271// TestPlanChangedWhenTrueRecomputesStatus pins #313: changed_when: 'true'
242272// on an in-sync task must flip the marker and the JSON status to a
243273// would-change verdict, not leave the stale [ok] / "status":"ok" the
0 commit comments