Skip to content

wftest YAML runner: add JSON path and header response assertions#370

Merged
intel352 merged 5 commits intomainfrom
copilot/add-json-path-header-assertions
Mar 24, 2026
Merged

wftest YAML runner: add JSON path and header response assertions#370
intel352 merged 5 commits intomainfrom
copilot/add-json-path-header-assertions

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 24, 2026

ResponseAssert only supported status and body (substring), making it impossible to assert specific JSON keys or response headers in YAML tests.

Changes

  • wftest/yaml_types.go — Added three fields to ResponseAssert:

    • json: dot-path → expected value (exact match)
    • json_not_empty: list of dot-paths that must be present and non-empty
    • headers: header name → expected value
  • wftest/yaml_runner.go — Extended applyAssertion to evaluate the new fields; added a jsonPath helper (dot-separated traversal into unmarshalled JSON, mirrors wftest/bdd/steps_assert.go)

  • wftest/yaml_runner_test.go — Added TestYAMLRunner_ResponseJSON exercising all three new assertion types

Example

assertions:
  - response:
      status: 200
      json:
        message: "Form created"
        data.id: "abc123"
      json_not_empty:
        - data
        - meta
      headers:
        Content-Type: "application/json"
Original prompt

This section details on the original issue you should resolve

<issue_title>wftest YAML runner: missing JSON path and header response assertions</issue_title>
<issue_description>## Summary

The YAML test runner's ResponseAssert struct only supports status (int) and body (substring match). The BDD framework supports richer response assertions that have no YAML equivalent:

  1. JSON path equality: the response JSON "key.path" should be "value" — dot-path traversal into JSON body
  2. JSON path non-empty: the response JSON "key" should not be empty
  3. Response header: the response header "X-Custom" should be "value"

Current behavior

assertions:
  - response:
      status: 200
      body: "some substring"    # only option for body checking

No way to assert that response.body.message == "Form created" or that a specific header is set.

Expected behavior

Extend ResponseAssert to support JSON path assertions and header checks:

assertions:
  - response:
      status: 200
      json:
        message: "Form created"        # exact match at JSON path
        data.id: "abc123"              # dot-path traversal
      json_not_empty:
        - data                          # assert path is non-empty
        - meta
      headers:
        Content-Type: "application/json"

Impact

We have ~20 BDD scenarios that use the response JSON "key" should be "value" assertions. These can only be approximated in YAML tests using body substring matching, which is less precise — e.g. body: "Form created" matches anywhere in the body, not specifically at the message key.

Suggested implementation

Type changes in yaml_types.go

type ResponseAssert struct {
    Status       int               `yaml:"status"`
    Body         string            `yaml:"body"`
    JSON         map[string]any    `yaml:"json"`           // dot-path → expected value
    JSONNotEmpty []string          `yaml:"json_not_empty"` // dot-paths that must be non-empty
    Headers      map[string]string `yaml:"headers"`        // header → expected value
}

Assertion logic in yaml_runner.go

The jsonPath() helper already exists in wftest/bdd/steps_assert.go and could be extracted to a shared location, or reimplemented in yaml_runner.go:

// JSON path assertions
for path, expected := range a.Response.JSON {
    val, err := jsonPath(result.RawBody, path)
    if err != nil {
        t.Errorf("assertion %s: %v", label, err)
        continue
    }
    if fmt.Sprintf("%v", val) != fmt.Sprintf("%v", expected) {
        t.Errorf("assertion %s: JSON %q: want %v, got %v", label, path, expected, val)
    }
}

// Header assertions
for header, expected := range a.Response.Headers {
    actual := result.Header(header)
    if actual != expected {
        t.Errorf("assertion %s: header %q: want %q, got %q", label, header, expected, actual)
    }
}

References

  • BDD JSON path assertions: wftest/bdd/steps_assert.go lines 161-189 (theResponseJSONShouldBe, theResponseJSONShouldNotBeEmpty)
  • BDD header assertion: wftest/bdd/steps_assert.go lines 192-201 (theResponseHeaderShouldBe)
  • YAML ResponseAssert: wftest/yaml_types.go lines 107-113</issue_description>

Comments on the Issue (you are @copilot in this section)


⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.

Copilot AI changed the title [WIP] Extend ResponseAssert to support JSON path and header assertions wftest YAML runner: add JSON path and header response assertions Mar 24, 2026
Copilot AI requested a review from intel352 March 24, 2026 16:30
@intel352 intel352 marked this pull request as ready for review March 24, 2026 17:16
Copilot AI review requested due to automatic review settings March 24, 2026 17:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds richer HTTP response assertions to the wftest YAML runner so YAML tests can assert specific JSON fields and response headers (parity with existing BDD assertions), rather than relying only on status and body substring checks.

Changes:

  • Extend ResponseAssert YAML schema with json, json_not_empty, and headers.
  • Implement evaluation logic for these new assertions in the YAML runner, including a dot-path JSON traversal helper.
  • Add a YAML runner test that exercises JSON path equality, JSON-not-empty, and header assertions.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
wftest/yaml_types.go Adds new YAML fields to ResponseAssert to express JSON-path and header expectations.
wftest/yaml_runner.go Implements the new assertion behaviors and adds a jsonPath helper.
wftest/yaml_runner_test.go Adds a new test YAML scenario validating JSON path + non-empty + header assertions.

Comment on lines +243 to +245
if fmt.Sprintf("%v", val) != fmt.Sprintf("%v", expected) {
t.Errorf("assertion %s: JSON %q: want %v, got %v", label, path, expected, val)
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Response.JSON assertions compare val and expected via fmt.Sprintf, which becomes unstable/incorrect for non-scalar JSON values (e.g., objects) because Go map iteration order is randomized and the string form can differ even when the JSON structures are equivalent. Consider comparing by marshaling both sides to JSON (or using a deep-equality that normalizes numeric types) to support exact matches reliably for nested values.

Suggested change
if fmt.Sprintf("%v", val) != fmt.Sprintf("%v", expected) {
t.Errorf("assertion %s: JSON %q: want %v, got %v", label, path, expected, val)
}
wantJSON, err := json.Marshal(expected)
if err != nil {
t.Errorf("assertion %s: JSON %q: cannot marshal expected value: %v", label, path, err)
continue
}
gotJSON, err := json.Marshal(val)
if err != nil {
t.Errorf("assertion %s: JSON %q: cannot marshal actual value: %v", label, path, err)
continue
}
if !bytes.Equal(wantJSON, gotJSON) {
t.Errorf("assertion %s: JSON %q: want %s, got %s", label, path, string(wantJSON), string(gotJSON))
}

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +255
for _, path := range a.Response.JSONNotEmpty {
val, err := jsonPath(result.RawBody, path)
if err != nil {
t.Errorf("assertion %s: %v", label, err)
continue
}
if val == nil || fmt.Sprintf("%v", val) == "" {
t.Errorf("assertion %s: JSON %q: expected non-empty, got %v", label, path, val)
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json_not_empty currently treats empty arrays/maps as non-empty because it only checks val == nil or fmt.Sprintf("%v", val) == "". This will incorrectly pass for {} and []. Consider handling common JSON container types explicitly (string/[]any/map[string]any) and checking length == 0 as empty.

Copilot uses AI. Check for mistakes.
Comment on lines +257 to +261
for header, expected := range a.Response.Headers {
actual := result.Header(header)
if actual != expected {
t.Errorf("assertion %s: header %q: want %q, got %q", label, header, expected, actual)
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header assertions use result.Header(header) which does a direct map lookup; this makes header matching case-sensitive even though HTTP header names are case-insensitive. Consider canonicalizing the asserted header name (e.g., via http.CanonicalHeaderKey) before lookup, or updating Result.Header to do case-insensitive matching.

Copilot uses AI. Check for mistakes.
Comment on lines +318 to +337
// jsonPath traverses a JSON body using a dot-separated path (e.g., "user.name").
// Returns the value at the path, or an error if the path cannot be traversed.
func jsonPath(body []byte, path string) (any, error) {
var root any
if err := json.Unmarshal(body, &root); err != nil {
return nil, fmt.Errorf("JSON path %q: invalid JSON body: %w", path, err)
}
parts := strings.Split(path, ".")
current := root
for _, part := range parts {
m, ok := current.(map[string]any)
if !ok {
return nil, fmt.Errorf("JSON path %q: cannot traverse into non-object at %q", path, part)
}
current, ok = m[part]
if !ok {
return nil, fmt.Errorf("JSON path %q: key %q not found", path, part)
}
}
return current, nil
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsonPath is now duplicated in both wftest/yaml_runner.go and wftest/bdd/steps_assert.go, which increases the risk of behavior drift if one implementation changes. Consider extracting it into a shared helper in the wftest package and reusing it from both places (and optionally parsing the JSON body once per response assertion block to avoid repeated unmarshalling).

Copilot uses AI. Check for mistakes.
Comment on lines +249 to +252
trigger:
type: http
method: GET
path: /hello
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test YAML sets trigger.method: GET, but the YAML runner’s fireTrigger path for type: http currently ignores method and always issues a GET. Consider removing method here to avoid implying it’s honored (or switch to type: http.get/get if the intent is to test GET semantics).

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:245: parsing iteration count: invalid syntax
baseline-bench.txt:324924: parsing iteration count: invalid syntax
baseline-bench.txt:613342: parsing iteration count: invalid syntax
baseline-bench.txt:910478: parsing iteration count: invalid syntax
baseline-bench.txt:1197591: parsing iteration count: invalid syntax
baseline-bench.txt:1506538: parsing iteration count: invalid syntax
benchmark-results.txt:245: parsing iteration count: invalid syntax
benchmark-results.txt:304311: parsing iteration count: invalid syntax
benchmark-results.txt:613477: parsing iteration count: invalid syntax
benchmark-results.txt:910343: parsing iteration count: invalid syntax
benchmark-results.txt:1167543: parsing iteration count: invalid syntax
benchmark-results.txt:1477903: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │       sec/op       │    sec/op      vs base              │
InterpreterCreation-4              3.240m ± 193%   3.532m ± 187%       ~ (p=0.394 n=6)
ComponentLoad-4                    3.561m ±   0%   3.808m ±   1%  +6.92% (p=0.002 n=6)
ComponentExecute-4                 1.919µ ±   2%   2.095µ ±   3%  +9.12% (p=0.002 n=6)
PoolContention/workers-1-4         1.078µ ±   1%   1.092µ ±  14%  +1.30% (p=0.006 n=6)
PoolContention/workers-2-4         1.081µ ±   3%   1.093µ ±   3%       ~ (p=0.058 n=6)
PoolContention/workers-4-4         1.078µ ±   0%   1.091µ ±   2%  +1.16% (p=0.002 n=6)
PoolContention/workers-8-4         1.084µ ±   2%   1.093µ ±   1%       ~ (p=0.145 n=6)
PoolContention/workers-16-4        1.080µ ±   2%   1.090µ ±   5%  +0.93% (p=0.026 n=6)
ComponentLifecycle-4               3.581m ±   0%   3.631m ±   1%  +1.41% (p=0.002 n=6)
SourceValidation-4                 2.221µ ±   0%   2.232µ ±   1%  +0.52% (p=0.017 n=6)
RegistryConcurrent-4               783.4n ±   3%   786.3n ±   5%       ~ (p=0.310 n=6)
LoaderLoadFromString-4             3.606m ±   0%   3.737m ±   1%  +3.63% (p=0.002 n=6)
geomean                            17.32µ          17.84µ         +2.98%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               2.027Mi ± 0%   2.027Mi ± 0%       ~ (p=0.615 n=6)
ComponentLoad-4                     2.180Mi ± 0%   2.180Mi ± 0%       ~ (p=0.848 n=6)
ComponentExecute-4                  1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4         1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                2.183Mi ± 0%   2.183Mi ± 0%       ~ (p=0.790 n=6)
SourceValidation-4                  1.984Ki ± 0%   1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                1.133Ki ± 0%   1.133Ki ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4              2.182Mi ± 0%   2.182Mi ± 0%       ~ (p=0.565 n=6)
geomean                             15.25Ki        15.25Ki       +0.00%
¹ all samples are equal

                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │     allocs/op      │  allocs/op   vs base                │
InterpreterCreation-4                15.68k ± 0%   15.68k ± 0%       ~ (p=1.000 n=6)
ComponentLoad-4                      18.02k ± 0%   18.02k ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                    25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4           25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                 18.07k ± 0%   18.07k ± 0%       ~ (p=1.000 n=6) ¹
SourceValidation-4                    32.00 ± 0%    32.00 ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                  2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4               18.06k ± 0%   18.06k ± 0%       ~ (p=1.000 n=6) ¹
geomean                               183.3         183.3       +0.00%
¹ all samples are equal

pkg: github.com/GoCodeAlone/workflow/middleware
                                  │ baseline-bench.txt │       benchmark-results.txt       │
                                  │       sec/op       │   sec/op     vs base              │
CircuitBreakerDetection-4                  285.7n ± 4%   287.4n ± 1%       ~ (p=0.307 n=6)
CircuitBreakerExecution_Success-4          22.48n ± 0%   22.50n ± 0%       ~ (p=0.063 n=6)
CircuitBreakerExecution_Failure-4          64.65n ± 1%   64.41n ± 1%       ~ (p=0.589 n=6)
geomean                                    74.60n        74.69n       +0.12%

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │        B/op        │    B/op     vs base                │
CircuitBreakerDetection-4                 144.0 ± 0%     144.0 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │     allocs/op      │ allocs/op   vs base                │
CircuitBreakerDetection-4                 1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │       sec/op       │    sec/op     vs base              │
JQTransform_Simple-4                     843.8n ± 32%   861.5n ± 29%       ~ (p=0.221 n=6)
JQTransform_ObjectConstruction-4         1.399µ ±  0%   1.415µ ±  1%  +1.07% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.247µ ±  0%   3.267µ ±  1%       ~ (p=0.065 n=6)
JQTransform_Complex-4                    37.37µ ±  0%   38.25µ ±  2%  +2.35% (p=0.002 n=6)
JQTransform_Throughput-4                 1.735µ ±  1%   1.753µ ±  0%  +1.07% (p=0.009 n=6)
SSEPublishDelivery-4                     72.00n ±  4%   72.30n ±  0%       ~ (p=0.310 n=6)
geomean                                  1.617µ         1.638µ        +1.27%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
JQTransform_Simple-4                   1.273Ki ± 0%     1.273Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4       1.773Ki ± 0%     1.773Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4              2.625Ki ± 0%     2.625Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                  16.22Ki ± 0%     16.22Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4               1.984Ki ± 0%     1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │     allocs/op      │ allocs/op   vs base                │
JQTransform_Simple-4                     10.00 ± 0%     10.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4         15.00 ± 0%     15.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4                30.00 ± 0%     30.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                    324.0 ± 0%     324.0 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4                 17.00 ± 0%     17.00 ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
                                    │ baseline-bench.txt │       benchmark-results.txt       │
                                    │       sec/op       │   sec/op     vs base              │
SchemaValidation_Simple-4                    1.091µ ± 5%   1.092µ ± 3%       ~ (p=0.818 n=6)
SchemaValidation_AllFields-4                 1.663µ ± 5%   1.677µ ± 1%       ~ (p=0.394 n=6)
SchemaValidation_FormatValidation-4          1.578µ ± 1%   1.573µ ± 2%       ~ (p=0.675 n=6)
SchemaValidation_ManySchemas-4               1.775µ ± 4%   1.820µ ± 4%       ~ (p=0.093 n=6)
geomean                                      1.501µ        1.513µ       +0.79%

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │        B/op        │    B/op     vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │     allocs/op      │ allocs/op   vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │       sec/op       │    sec/op     vs base               │
EventStoreAppend_InMemory-4                1.228µ ± 24%   1.202µ ± 12%        ~ (p=0.310 n=6)
EventStoreAppend_SQLite-4                  1.711m ± 15%   1.546m ±  4%   -9.61% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           14.43µ ±  4%   13.92µ ±  3%   -3.55% (p=0.026 n=6)
GetTimeline_InMemory/events-50-4           78.84µ ± 20%   61.07µ ± 20%  -22.54% (p=0.004 n=6)
GetTimeline_InMemory/events-100-4          126.5µ ±  0%   121.7µ ±  1%   -3.82% (p=0.002 n=6)
GetTimeline_InMemory/events-500-4          651.0µ ±  1%   633.8µ ±  1%   -2.65% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.340m ±  0%   1.286m ±  1%   -4.02% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             111.2µ ±  1%   113.1µ ±  1%   +1.66% (p=0.004 n=6)
GetTimeline_SQLite/events-50-4             253.4µ ±  1%   258.2µ ±  1%   +1.89% (p=0.002 n=6)
GetTimeline_SQLite/events-100-4            425.7µ ±  1%   428.6µ ±  1%   +0.69% (p=0.041 n=6)
GetTimeline_SQLite/events-500-4            1.819m ±  0%   1.831m ±  2%        ~ (p=0.240 n=6)
GetTimeline_SQLite/events-1000-4           3.539m ±  0%   3.583m ±  3%   +1.23% (p=0.041 n=6)
geomean                                    228.6µ         220.0µ         -3.76%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  790.0 ± 6%     747.0 ± 7%       ~ (p=0.102 n=6)
EventStoreAppend_SQLite-4                  1.982Ki ± 2%   1.986Ki ± 1%       ~ (p=0.366 n=6)
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%   7.953Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%   46.62Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%   94.48Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%   472.8Ki ± 0%       ~ (p=0.242 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=0.061 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ± 0%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ± 0%  -0.00% (p=0.009 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%  +0.00% (p=0.032 n=6)
geomean                                    67.34Ki        67.04Ki       -0.45%
¹ all samples are equal

                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │     allocs/op      │  allocs/op   vs base                │
EventStoreAppend_InMemory-4                  7.000 ± 0%    7.000 ± 0%       ~ (p=1.000 n=6) ¹
EventStoreAppend_SQLite-4                    53.00 ± 0%    53.00 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-10-4             125.0 ± 0%    125.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4             653.0 ± 0%    653.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4           1.306k ± 0%   1.306k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4           6.514k ± 0%   6.514k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-1000-4          13.02k ± 0%   13.02k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-10-4               382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.852k ± 0%   1.852k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             3.681k ± 0%   3.681k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             18.54k ± 0%   18.54k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            37.29k ± 0%   37.29k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.162k        1.162k       +0.00%
¹ all samples are equal

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

@intel352
Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot apply changes based on the comments in this thread

Applied all 5 review changes in commit 83854ff:

  1. JSON comparison — replaced fmt.Sprintf with json.Marshal for reliable comparison of nested/object values
  2. json_not_empty empty containers — replaced the string-check with IsJSONEmpty() which handles nil, "", [], and {}
  3. Header case-insensitivity — applies http.CanonicalHeaderKey(header) before looking up in the result headers map
  4. Shared jsonPath — extracted to wftest/json_path.go as JSONPath and IsJSONEmpty; both yaml_runner.go and bdd/steps_assert.go now use the shared helpers (eliminating the duplicate)
  5. Test method: GET — removed the redundant field from the test trigger

@intel352 intel352 merged commit 4745a3a into main Mar 24, 2026
18 checks passed
@intel352 intel352 deleted the copilot/add-json-path-header-assertions branch March 24, 2026 21:21
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.

wftest YAML runner: missing JSON path and header response assertions

3 participants