|
1 | 1 | package api_test |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "errors" |
5 | 4 | "log/slog" |
6 | 5 | "testing" |
7 | 6 |
|
@@ -179,59 +178,6 @@ func TestDispatchAction_PreconditionNoTaskQueue(t *testing.T) { |
179 | 178 | assert.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err)) |
180 | 179 | } |
181 | 180 |
|
182 | | -// TestDispatchAction_TemplatedParamsRefused pins the templated-params |
183 | | -// gate added for #196 scope correction. Variables resolve only via |
184 | | -// device-group / user-group memberships at agent SyncActions time; an |
185 | | -// ad-hoc DispatchAction has no group context so the renderer can't |
186 | | -// resolve `{{ var.NAME }}` references. The gate refuses with |
187 | | -// CodeFailedPrecondition rather than enqueueing literal `{{ ... }}` |
188 | | -// markers the agent would consume verbatim. |
189 | | -func TestDispatchAction_TemplatedParamsRefused(t *testing.T) { |
190 | | - st := testutil.SetupPostgres(t) |
191 | | - h := api.NewActionHandler(st, slog.Default(), api.NoOpSigner{}) |
192 | | - |
193 | | - adminID := testutil.CreateTestUser(t, st, testutil.NewID()+"@test.com", "pass", "admin") |
194 | | - deviceID := testutil.CreateTestDevice(t, st, "templated-host") |
195 | | - ctx := testutil.AdminContext(adminID) |
196 | | - |
197 | | - _, err := h.DispatchAction(ctx, connect.NewRequest(&pm.DispatchActionRequest{ |
198 | | - DeviceId: deviceID, |
199 | | - ActionSource: &pm.DispatchActionRequest_InlineAction{ |
200 | | - InlineAction: &pm.Action{ |
201 | | - Id: &pm.ActionId{Value: testutil.NewID()}, |
202 | | - Type: pm.ActionType_ACTION_TYPE_SHELL, |
203 | | - DesiredState: pm.DesiredState_DESIRED_STATE_PRESENT, |
204 | | - Params: &pm.Action_Shell{ |
205 | | - Shell: &pm.ShellParams{ |
206 | | - Script: "echo {{ var.greeting }}", |
207 | | - }, |
208 | | - }, |
209 | | - }, |
210 | | - }, |
211 | | - })) |
212 | | - require.Error(t, err) |
213 | | - assert.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err), |
214 | | - "templated params on ad-hoc dispatch MUST surface FailedPrecondition — anything else lets literal {{ var.X }} reach the agent verbatim") |
215 | | - assert.Contains(t, err.Error(), "templated parameters", |
216 | | - "error message must explain that variables are group-only and the action should be assigned to a group instead") |
217 | | - |
218 | | - // The web client reads the structured ErrorDetail code (not the |
219 | | - // human-readable message) to map the failure to a localized |
220 | | - // "assign to a group instead" UI message. Pin the code here so a |
221 | | - // future refactor that drops the structured detail breaks the test |
222 | | - // instead of silently breaking the web error mapping. |
223 | | - var connectErr *connect.Error |
224 | | - require.True(t, errors.As(err, &connectErr)) |
225 | | - details := connectErr.Details() |
226 | | - require.NotEmpty(t, details, "templated-dispatch refusal MUST carry the structured ErrorDetail — the web client maps by code, not by message") |
227 | | - val, vErr := details[0].Value() |
228 | | - require.NoError(t, vErr) |
229 | | - detail, ok := val.(*pm.ErrorDetail) |
230 | | - require.True(t, ok, "first detail must be ErrorDetail; got %T", val) |
231 | | - assert.Equal(t, api.ErrTemplatedDispatchRefused, detail.Code, |
232 | | - "error code MUST be templated_dispatch_refused — the web client switches on this exact string") |
233 | | -} |
234 | | - |
235 | 181 | // TestDispatchInstantAction_PreconditionNoTaskQueue pins the |
236 | 182 | // matching fail-closed behaviour for the instant-action path. |
237 | 183 | func TestDispatchInstantAction_PreconditionNoTaskQueue(t *testing.T) { |
|
0 commit comments