Skip to content

Commit cedf1e4

Browse files
easelclaude
andcommitted
fix(graphql,test): explainInputs parser + textarea assertions for schemas-tab e2e
Two issues surfaced when the new policy-authoring (schemas tab) e2e tests ran against a live axon-server: 1. axon-graphql/src/dynamic.rs: putSchema(input.explainInputs) entries were wrapped in a synthetic { input: <entry> } object before being handed to explain_policy_request_from_value. That extractor uses input_object(value, "input") only as an error label — the second argument is not dereferenced — so the wrapping caused the lookup of `operation` to miss and return "operation must be a string". Strip the wrap and pass each entry through directly. 2. ui/tests/e2e/policy-authoring.spec.ts: the happy-path test was using toContainText against the textarea, but textarea content lives on the `value` attribute (toHaveValue), and the patch fixture dry-run was missing the patch JSON payload required by explain_patch_policy. Switched to financeAgent for the update.allow rule plus a patch JSON that crosses the large-invoice threshold so the require-approval-large-invoice-update envelope decides needs_approval with finance-approver. Both schemas-tab specs and the new intent-audit-lineage audit-evidence test pass against axon-cli/serve --no-auth in-memory. Adjacent specs (mutation-intents, approval-inbox, graphql-policy-console, mcp-envelope-preview, original policy-authoring) all green: 17/17. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f7d6adb commit cedf1e4

2 files changed

Lines changed: 23 additions & 16 deletions

File tree

crates/axon-graphql/src/dynamic.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6693,6 +6693,11 @@ async fn put_schema_resolver<S: StorageAdapter + 'static>(
66936693
}
66946694

66956695
/// Parse a list of `ExplainPolicyInput` values from `putSchema(input.explainInputs)`.
6696+
///
6697+
/// `ExplainPolicyInput` shares its shape with the active `explainPolicy`
6698+
/// query input — the extractor expects the bare object (its `input_object`
6699+
/// helper just casts to JSON object for an error label, it does not
6700+
/// dereference an outer `input` field).
66966701
fn explain_policy_dry_run_inputs_from_value(
66976702
value: &Value,
66986703
) -> Result<Vec<ExplainPolicyRequest>, GqlError> {
@@ -6703,20 +6708,10 @@ fn explain_policy_dry_run_inputs_from_value(
67036708
})?;
67046709
entries
67056710
.iter()
6706-
.map(explain_policy_dry_run_input_from_value)
6711+
.map(explain_policy_request_from_value)
67076712
.collect()
67086713
}
67096714

6710-
fn explain_policy_dry_run_input_from_value(
6711-
value: &Value,
6712-
) -> Result<ExplainPolicyRequest, GqlError> {
6713-
// ExplainPolicyInput uses the same shape as the active explainPolicy
6714-
// query; reuse the extractor by wrapping in a synthetic { input: ... }
6715-
// object so explain_policy_request_from_value's lookup works.
6716-
let wrapped = json!({ "input": value });
6717-
explain_policy_request_from_value(&wrapped)
6718-
}
6719-
67206715
// ── Schema builders ─────────────────────────────────────────────────────────
67216716

67226717
fn policy_plans_by_collection(

ui/tests/e2e/policy-authoring.spec.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ test.describe('Policy authoring (schemas tab)', () => {
127127
// Open the Policy view.
128128
await page.getByTestId('schema-policy-view-toggle').click();
129129
await expect(page.getByTestId('schema-policy-view')).toBeVisible();
130-
await expect(page.getByTestId('schema-policy-editor')).toContainText(
131-
'finance-and-operators-read-invoices',
130+
// Textareas surface their text via `value`, not innerText.
131+
await expect(page.getByTestId('schema-policy-editor')).toHaveValue(
132+
/finance-and-operators-read-invoices/,
132133
);
133134

134135
// Replace the editor contents with the proposed (tightened) policy.
@@ -144,11 +145,22 @@ test.describe('Policy authoring (schemas tab)', () => {
144145
SCN017_ROLES.financeApprover,
145146
);
146147

147-
// Fixture dry-run as the finance approver: a large invoice patch routes
148-
// through the `require-approval-large-invoice-update` envelope.
149-
await page.getByTestId('schema-policy-fixture-subject').fill(SCN017_SUBJECTS.financeApprover);
148+
// Fixture dry-run as the finance agent: a large invoice patch passes
149+
// the update.allow rule and then routes through the
150+
// require-approval-large-invoice-update envelope, requiring the
151+
// finance-approver role.
152+
await page.getByTestId('schema-policy-fixture-subject').fill(SCN017_SUBJECTS.financeAgent);
150153
await page.getByTestId('schema-policy-fixture-operation').selectOption('patch');
151154
await page.getByTestId('schema-policy-fixture-entity').fill(fixture.invoices.large.id);
155+
await page
156+
.getByTestId('schema-policy-fixture-patch')
157+
.fill(
158+
JSON.stringify(
159+
{ amount_cents: fixture.invoices.large.amountCents + 500_000 },
160+
null,
161+
2,
162+
),
163+
);
152164
await page.getByTestId('schema-policy-fixture-run').click();
153165
await expect(page.getByTestId('schema-policy-fixture-decision')).toContainText(
154166
'needs_approval',

0 commit comments

Comments
 (0)