Skip to content

feat: add GraphQL request support#12

Merged
techygarg merged 2 commits into
mainfrom
feat/add-graphql-support
Jul 10, 2026
Merged

feat: add GraphQL request support#12
techygarg merged 2 commits into
mainfrom
feat/add-graphql-support

Conversation

@techygarg

Copy link
Copy Markdown
Owner

Summary

  • Add a graphql block (query/queryFromFile/variables/operationName) on api.request and mock interaction requests — TestCaseResolver composes the standard { query, variables, operationName } body at hydration time, defaults method to POST only if unset, and adds Content-Type: application/json only if not already present.
  • Add array-wildcard * segment support to ignore/pattern matcher paths (e.g. errors__*__path) via normalize-and-compare on array indices, so a GraphQL errors[] array can be matched regardless of length.
  • Wire HotChocolate into the example User.Api (Query/Mutation resolvers, UserErrorFilter for error extensions) and add component + integration test coverage.
  • Add doc/graphql-support.md and update test-file-format.md, matchers-and-patterns.md, README.md, Package.Readme.md, and CLAUDE.md accordingly.

Test plan

  • make unit — 268/268 passing (net9.0 + net10.0)
  • make component — 20/20 passing
  • make integration — 29/29 passing

Add a `graphql` block (query/queryFromFile/variables/operationName) on
api.request and mock interaction requests. TestCaseResolver composes it
into the standard { query, variables, operationName } body at hydration
time, defaults method to POST only if unset, and adds
Content-Type: application/json only if not already present.

Add array-wildcard `*` segment support to ignore/pattern matcher paths
(e.g. errors__*__path) via normalize-and-compare on array indices, so a
GraphQL errors[] array can be matched regardless of length.

Wire HotChocolate into the example User.Api (Query/Mutation resolvers,
UserErrorFilter for error extensions) and add component + integration
test coverage. Add doc/graphql-support.md and update test-file-format.md,
matchers-and-patterns.md, README, and CLAUDE.md accordingly.
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add GraphQL request DSL and wildcard array matching for matchers

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a request.graphql DSL block that composes GraphQL request envelopes at hydration time.
• Extend ignore/pattern matcher paths with * to match fields across array elements.
• Update example User.Api with HotChocolate GraphQL + add unit/component/integration coverage and
 docs.
Diagram

graph TD
  A["Test case (YAML/JSON)"] --> B["TestCaseResolver"] --> C["HttpTestRequest"] --> D["HTTP execution"] --> E["GraphQL endpoint"] --> F["Response JSON"] --> G["ResultMatcher"]
  B --> H["GraphqlRequest"]
  subgraph Legend
    direction LR
    _doc["DSL / docs"] ~~~ _svc["Service / component"] ~~~ _mod["Library module"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Compose GraphQL envelope at execution time (TestHttpClient)
  • ➕ Keeps all request-building logic in the execution layer
  • ➕ Could support future transports (e.g., multipart uploads) in one place
  • ➖ Requires plumbing GraphQL awareness into runtime execution paths
  • ➖ Harder to keep mocks and real HTTP requests consistent without duplicating logic
  • ➖ Violates existing pattern where downstream only consumes an already-composed Body/Method/Headers
2. Implement wildcard parent matching via regex translation
  • ➕ Directly maps * to an index pattern (e.g., \[\d+\])
  • ➕ Potentially extensible to richer globbing
  • ➖ Must correctly escape arbitrary user-provided literal segments
  • ➖ Higher risk of metacharacter bugs and surprising matches vs. strict normalization
3. Flatten GraphQL fields onto HttpTestRequest (no nested block)
  • ➕ One fewer DTO and nesting layer
  • ➕ Slightly simpler deserialization
  • ➖ Doesn’t match the intended DSL shape (graphql: block)
  • ➖ Increases top-level request surface area and risks name collisions

Recommendation: Keep the current approach: compiling graphql into the standard request body in TestCaseResolver minimizes impact and keeps the runtime HTTP/mock pipeline unchanged. The normalize-and-compare strategy for array wildcards is a good tradeoff for correctness and backward compatibility versus regex-based matching.

Files changed (31) +1438 / -37

Enhancement (8) +129 / -2
Mutation.csAdd GraphQL mutation root mapped to MediatR command +13/-0

Add GraphQL mutation root mapped to MediatR command

• Introduces a HotChocolate mutation type exposing 'createUser(input)' and forwarding execution to existing MediatR handlers.

example/User.Api/GraphQL/Mutation.cs

Query.csAdd GraphQL query root mapped to MediatR query +13/-0

Add GraphQL query root mapped to MediatR query

• Introduces a HotChocolate query type exposing 'userById(id)' and forwarding to the existing 'UserByIdQuery' MediatR handler.

example/User.Api/GraphQL/Query.cs

UserErrorFilter.csAdd GraphQL error filter to produce consistent extensions.code +24/-0

Add GraphQL error filter to produce consistent extensions.code

• Adds an 'IErrorFilter' that maps known exception types to stable error codes and ensures 'errors[].extensions.code' is present for assertions.

example/User.Api/GraphQL/UserErrorFilter.cs

Startup.csWire HotChocolate GraphQL server into example API +12/-1

Wire HotChocolate GraphQL server into example API

• Registers the GraphQL server with query/mutation types and an error filter. Maps '/graphql' endpoint alongside existing MVC routes.

example/User.Api/Startup.cs

ResultMatcher.csAdd array-wildcard '*' support to parent-path matching +18/-1

Add array-wildcard '*' support to parent-path matching

• Extends 'IsParentMatching' to normalize concrete array indices (e.g., '[0]') and match them against templates containing '*' segments. Preserves the existing empty-parent-path behavior as an unconditional match.

src/ConfIT/Matching/ResultMatcher.cs

GraphqlRequest.csAdd GraphqlRequest DTO for request.graphql DSL block +9/-0

Add GraphqlRequest DTO for request.graphql DSL block

• Introduces a new model class representing GraphQL query text or file reference, variables payload, and optional operation name.

src/ConfIT/Model/GraphqlRequest.cs

HttpTestRequest.csAdd optional Graphql block to HttpTestRequest +1/-0

Add optional Graphql block to HttpTestRequest

• Extends the request model with a 'GraphqlRequest? Graphql' property to support declarative GraphQL requests in test files.

src/ConfIT/Model/HttpTestRequest.cs

TestCaseResolver.csHydrate GraphQL block into standard HTTP body/method/headers +39/-0

Hydrate GraphQL block into standard HTTP body/method/headers

• Adds GraphQL hydration for both API requests and mock interaction requests. Composes '{query, variables, operationName}' JSON, defaults method to POST only when unset, and adds 'Content-Type: application/json' only if absent (case-insensitive), with 'queryFromFile' support and validation errors.

src/ConfIT/Reader/TestCaseResolver.cs

Tests (7) +686 / -2
05-graphql.yamlAdd component test cases exercising GraphQL DSL and wildcard matchers +154/-0

Add component test cases exercising GraphQL DSL and wildcard matchers

• Adds a GraphQL-focused component test file covering mutation + extract, inline query with injection, queryFromFile, and multi-error responses with wildcard ignores. Includes mock interactions needed for the example domain flow.

example/User.ComponentTests/TestCase/05-graphql.yaml

user-by-id.graphqlAdd reusable GraphQL query fixture for component tests +8/-0

Add reusable GraphQL query fixture for component tests

• Adds a '.graphql' request fixture consumed via 'graphql.queryFromFile'.

example/User.ComponentTests/TestCase/Request/user-by-id.graphql

08-graphql.yamlAdd integration test cases for GraphQL DSL and wildcard matchers +129/-0

Add integration test cases for GraphQL DSL and wildcard matchers

• Mirrors the component suite GraphQL scenarios against the real integration setup (no mocks), covering mutation + extract, inline query injection, queryFromFile, and multi-error wildcard ignores.

example/User.IntegrationTests/TestCase/08-graphql.yaml

user-by-id.graphqlAdd reusable GraphQL query fixture for integration tests +8/-0

Add reusable GraphQL query fixture for integration tests

• Adds a '.graphql' request fixture for 'queryFromFile' in integration tests.

example/User.IntegrationTests/TestCase/Request/user-by-id.graphql

TestCaseResolverTests.csAdd unit tests for GraphQL request hydration and precedence +226/-2

Add unit tests for GraphQL request hydration and precedence

• Adds coverage for inline query composition, variables/operationName inclusion, queryFromFile byte-for-byte reading, file precedence over inline query, validation failures, method/header defaulting rules, mock interaction hydration, and graphql-over-bodyFromFile precedence.

test/ConfIT.UnitTest/Reader/TestCaseResolverTests.cs

HttpMockServerTests.csTest mock disambiguation for multiple GraphQL bodies on same path +62/-0

Test mock disambiguation for multiple GraphQL bodies on same path

• Adds a unit test ensuring two mock interactions sharing '/graphql' are disambiguated by request body content and each returns its own configured response.

test/ConfIT.UnitTest/Server/Mock/HttpMockServerTests.cs

ResultMatcherTests.csAdd unit tests for array-wildcard ignore/pattern behavior +99/-0

Add unit tests for array-wildcard ignore/pattern behavior

• Adds tests verifying wildcard ignore removes fields across all array entries, wildcard pattern failures point to concrete failing indices, empty arrays are handled safely, multi-level wildcards work, and non-wildcard behavior remains unchanged.

test/ConfIT.UnitTest/Util/ResultMatcherTests.cs

Documentation (12) +607 / -33
graphql-support.mdAdd design/context doc for GraphQL support implementation +166/-0

Add design/context doc for GraphQL support implementation

• Introduces a full design record for GraphQL request hydration and array-wildcard matching, including decisions, component boundaries, precedence rules, and verification notes. Documents why compilation happens in 'TestCaseResolver' and why wildcard matching uses normalization rather than regex.

.lattice/context/graphql-support.md

operational-learnings.mdRelocate and extend operational learnings +26/-0

Relocate and extend operational learnings

• Adds a reorganized learnings document under '.lattice/learnings/', capturing design and implementation lessons relevant to this PR (JToken null round-tripping, wildcard matching strategy, DTO wiring).

.lattice/learnings/operational-learnings.md

platform-play.mdLink INT-002 GraphQL support feature spec from epic index +1/-1

Link INT-002 GraphQL support feature spec from epic index

• Updates the Platform Play epic list to link to the new GraphQL feature requirement doc.

.lattice/requirements/epics/platform-play.md

int-002-graphql-support.mdAdd GraphQL support requirements and acceptance criteria +134/-0

Add GraphQL support requirements and acceptance criteria

• Defines the problem statement, scope, scenarios, constraints, and acceptance criteria for GraphQL request composition and array-wildcard matcher behavior.

.lattice/requirements/features/int-002-graphql-support.md

index.mdUpdate requirements index timestamp +1/-1

Update requirements index timestamp

• Bumps the 'last_updated' date in the requirements index.

.lattice/requirements/index.md

CLAUDE.mdUpdate repo guidance to include GraphQL + wildcard matcher support +43/-29

Update repo guidance to include GraphQL + wildcard matcher support

• Refreshes project overview and folder map to match the current architecture and newly shipped capabilities. Adds explicit notes about 'graphql' request hydration and array-wildcard matcher segments.

CLAUDE.md

README.mdDocument GraphQL support and wildcard matcher segments +2/-1

Document GraphQL support and wildcard matcher segments

• Adds a link to the new GraphQL guide and updates matcher documentation references to include array-wildcard '*' support.

README.md

Package.Readme.mdAdd GraphQL support callout to package readme +2/-0

Add GraphQL support callout to package readme

• Highlights the 'graphql' request block as a first-class capability alongside existing matchers, extract/inject, and mocking.

doc/Package.Readme.md

doc-strategy.mdAdd GraphQL doc to documentation strategy and indexes +3/-1

Add GraphQL doc to documentation strategy and indexes

• Updates the doc inventory to include 'graphql-support.md' and expands matcher documentation coverage to mention array-wildcard segments.

doc/doc-strategy.md

graphql-support.mdAdd user-facing GraphQL DSL reference +186/-0

Add user-facing GraphQL DSL reference

• Documents the 'request.graphql' structure, composition rules (body/method/headers), 'queryFromFile' behavior, usage with extract/inject, error-array matching via wildcards, and mock usage guidance.

doc/graphql-support.md

matchers-and-patterns.mdDocument array-wildcard '*' segments for ignore/pattern +25/-0

Document array-wildcard '*' segments for ignore/pattern

• Adds an explicit section describing '*' segments in '__' paths, with examples and constraints (non-leaf usage). Updates the matcher summary table to include the new capability.

doc/matchers-and-patterns.md

test-file-format.mdAdd 'graphql' request field to DSL format reference +18/-0

Add 'graphql' request field to DSL format reference

• Extends the request field table and adds a new section documenting 'request.graphql' as an alternative to raw 'body', referencing the full GraphQL guide.

doc/test-file-format.md

Other (4) +16 / -0
User.Api.csprojAdd HotChocolate.AspNetCore dependency to example API +1/-0

Add HotChocolate.AspNetCore dependency to example API

• Adds the HotChocolate ASP.NET Core package reference required to host the example GraphQL endpoint.

example/User.Api/User.Api.csproj

User.ComponentTests.csprojCopy GraphQL test assets to output for component suite +8/-0

Copy GraphQL test assets to output for component suite

• Ensures the new GraphQL YAML test file and '.graphql' request fixture are copied to the test output directory.

example/User.ComponentTests/User.ComponentTests.csproj

suite.config.yamlConfigure RequestBody folder for GraphQL queryFromFile fixtures +1/-0

Configure RequestBody folder for GraphQL queryFromFile fixtures

• Adds 'folders.requestBody' pointing to 'TestCase/Request' so 'queryFromFile' can resolve '.graphql' fixtures.

example/User.ComponentTests/suite.config.yaml

User.IntegrationTests.csprojCopy GraphQL integration test assets to output +6/-0

Copy GraphQL integration test assets to output

• Ensures the new GraphQL YAML test file and '.graphql' request fixture are included in build output for integration tests.

example/User.IntegrationTests/User.IntegrationTests.csproj

@qodo-code-review

qodo-code-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 17 rules

Grey Divider


Remediation recommended

1. Wildcard matching overhead ✓ Resolved 🐞 Bug ➹ Performance
Description
ResultMatcher.RemoveField performs a full recursive walk of the JSON tree and now calls
IsParentMatching which runs Regex.Replace and rebuilds the parent template string on every
visited property. This adds avoidable per-node allocations/regex work in a hot path and can slow
down matching on large responses or many ignore/pattern rules.
Code

src/ConfIT/Matching/ResultMatcher.cs[R82-98]

    private static bool IsParentMatching(JProperty prop, string parentsKey) =>
-        string.IsNullOrWhiteSpace(parentsKey) || prop.Parent!.Path.Equals(parentsKey);
+        // Empty parentsKey must keep matching any parent unconditionally — this short-circuit
+        // must stay first and must not be folded into the normalized comparison below.
+        string.IsNullOrWhiteSpace(parentsKey)
+        || NormalizeArrayIndices(prop.Parent!.Path).Equals(BuildParentTemplate(parentsKey));
+
+    private static string NormalizeArrayIndices(string path) =>
+        Regex.Replace(path, @"\[\d+\]", "[*]");
+
+    private static string BuildParentTemplate(string parentsKey)
+    {
+        var template = "";
+        foreach (var segment in parentsKey.Split('.'))
+            template += segment == Wildcard ? "[*]" : (template.Length > 0 ? "." : "") + segment;
+
+        return template;
+    }
Evidence
The recursive matcher walk invokes IsParentMatching for every JProperty, and IsParentMatching
now performs regex replacement and template construction each time, multiplying the cost by the
number of visited properties and matcher rules.

src/ConfIT/Matching/ResultMatcher.cs[64-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ResultMatcher.RemoveField` recursively visits every JSON node and calls `IsParentMatching` for each visited `JProperty`. The new wildcard support makes `IsParentMatching` run `Regex.Replace(...)` and rebuild a template via `Split('.')` + concatenation on every invocation, creating unnecessary allocations and regex work in a tight loop.

### Issue Context
- `RemoveField` is called once per ignore/pattern entry and performs a full-tree traversal.
- `IsParentMatching` is invoked for each `JProperty` encountered during that traversal.

### Fix Focus Areas
- src/ConfIT/Matching/ResultMatcher.cs[64-99]

### Suggested fix approach
1. Only normalize when needed:
  - If `parentsKey` is empty: keep the fast-path.
  - If `parentsKey` does **not** contain the wildcard segment (`*`) and `prop.Parent!.Path` contains no `[` then keep a simple equality check.
2. Avoid regex per call:
  - Introduce a static compiled regex (e.g., `private static readonly Regex ArrayIndexRegex = new(@"\[\d+\]", RegexOptions.Compiled);`) and use `ArrayIndexRegex.Replace(...)`.
3. Cache the built parent template:
  - Precompute `BuildParentTemplate(parentsKey)` once per matcher rule (in `ApplyIgnoreMatcher` / `ApplyPatternMatcher`) and pass the precomputed template into `RemoveField`/`IsParentMatching`, or memoize templates in a small dictionary keyed by `parentsKey` within a single `MatchResponseBody` execution.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/ConfIT/Matching/ResultMatcher.cs Outdated
…atching

BuildParentTemplate and array-index regex normalization ran once per
matching JProperty during tree traversal. Hoist template construction
to once per matcher rule, use a compiled static regex, and skip
normalization entirely when neither the template nor the path contains
an array index.
@techygarg
techygarg merged commit d194755 into main Jul 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant