Skip to content

test(es): Validate spec-conformant NDJSON in the request snapshot har… - #9111

Merged
jkowall merged 7 commits into
jaegertracing:mainfrom
hharshhsaini:fix/snapshottest-ndjson-validation
Aug 1, 2026
Merged

test(es): Validate spec-conformant NDJSON in the request snapshot har…#9111
jkowall merged 7 commits into
jaegertracing:mainfrom
hharshhsaini:fix/snapshottest-ndjson-validation

Conversation

@hharshhsaini

Copy link
Copy Markdown
Contributor

Which problem is this PR solving?

Description of the changes

The harness renders _bulk/_msearch bodies as a canonicalized JSON array under an "ndjson" key, which proves the shape but not that the bytes are NDJSON the backend will accept. Since reviewers only ever see the canonical form, three
regressions were invisible in a diff:

  • bytes.TrimRight(r.Body, "\n") ran before analysis, so a body missing the final newline the _bulk API requires snapshotted identically to a correct one;
  • parseNDJSON skipped blank lines, so {a}\n\n{b} snapshotted the same as {a}\n{b};
  • CapturedRequest recorded no headers, so Content-Type was never checked.

Changes:

  • CapturedRequest now records ContentType, captured in ServeHTTP.
  • New validateNDJSON(contentType, body) error checks the media type, a non-empty body, and exactly one trailing newline. toSnapshot applies it to _bulk/ _msearch paths. Returning an error (rather than asserting inside the helper)
    keeps the rules unit-testable directly, with no stub testing.TB.
  • parseNDJSON rejects blank lines instead of skipping them.

One deviation worth review. The issue asks to validate Content-Type is application/x-ndjson. Implemented strictly, that fails TestWriterRequestSnapshots: esapi.BulkRequest in the official go-elasticsearch client sets application/json
when the caller leaves the header unset, so our async bulk indexer emits it. I therefore accept both application/x-ndjson and application/json. If you'd rather the async path be normalized to x-ndjson and the check tightened, happy
to do that instead, it's a client change rather than a harness one, so I kept it out of this PR.

No snapshot fixtures change: the new rules are assertions, not additions to the canonical output.

How was this change tested?

  • TestValidateNDJSON table-covers accepted media types (incl. a charset parameter), missing/wrong types, empty body, missing trailing newline, and an extra trailing newline.
  • TestParseNDJSONRejectsBlankLines covers leading, interior, and whitespace-only blank lines, plus the valid two-document case.
  • TestIsNDJSONEndpoint covers _bulk/_msearch vs _search/_doc.
  • TestRecorderCapturesNDJSON now asserts the Content-Type is captured.
  • Verified each guard actually fires (not vacuous) against a deliberately malformed body, and that all existing ES snapshot suites still pass unchanged.
  • make fmt, make lint, make test all pass.

Checklist

AI Usage in this PR (choose one)

See AI Usage Policy.

  • None: No AI tools were used in creating this PR
  • Light: AI provided minor assistance (formatting, simple suggestions)
  • Moderate: AI helped with code generation or debugging specific parts
  • Heavy: AI generated most or all of the code changes

…ness

The harness canonicalizes _bulk/_msearch bodies into a JSON array under an
"ndjson" key, which proves the shape but not that the bytes are NDJSON the
backend accepts. Reviewers only ever see the canonical form, so three classes
of regression were invisible in a diff:

- the trailing newline was stripped by TrimRight before analysis, so a body
  missing the final newline the _bulk API requires snapshotted identically to
  a correct one;
- parseNDJSON skipped blank lines, so "{a}\n\n{b}" snapshotted the same as
  "{a}\n{b}";
- CapturedRequest recorded no headers, so the request's Content-Type was never
  checked at all.

Capture the Content-Type, and validate NDJSON endpoints against a pure
validateNDJSON helper: an accepted media type, a non-empty body, and exactly
one trailing newline. parseNDJSON now rejects blank lines instead of skipping
them. Keeping the rules in a function that returns an error makes them
unit-testable directly, without a stub testing.TB.

The media type is checked against both application/x-ndjson and
application/json: esapi.BulkRequest in the official go-elasticsearch client
sets application/json when the caller leaves the header unset, so the async
bulk indexer emits it, and requiring x-ndjson alone would reject a request the
official client itself produces.

No snapshot fixtures change: the new rules are assertions, not additions to
the canonical output.

Signed-off-by: hharshhsaini <sainiharsh3311@gmail.com>
@jkowall jkowall added the changelog:test Change that's adding missing tests or correcting existing tests label Jul 26, 2026

@jkowall jkowall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One edge case remains in the NDJSON validity guarantee; details inline.

Comment thread internal/storage/elasticsearch/snapshottest/snapshottest.go
A body of only "\n" passed validateNDJSON: it is non-empty, ends with exactly
one newline, and is not "\n\n". toSnapshot then trims it to empty and returns
before parseNDJSON runs, so the blank line was never rejected and validation
returned nil for a body carrying no document — which the backend would reject.

Reject a body with no non-whitespace content (len(TrimSpace(body)) == 0) and
add newline-only and whitespace-only cases to TestValidateNDJSON.

Signed-off-by: hharshhsaini <sainiharsh3311@gmail.com>

@jkowall jkowall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The newline-only body fix is correct, but the Content-Type validation still has one spec-conformance gap; details inline.

Comment thread internal/storage/elasticsearch/snapshottest/snapshottest.go Outdated
validateNDJSON split the Content-Type on the first ";" with strings.Cut, which
accepted a header with a malformed parameter ("application/x-ndjson; garbage")
and rejected a valid mixed-case one ("Application/X-NDJSON") because the compare
was case-sensitive.

Use mime.ParseMediaType: it validates the parameters (rejecting the malformed
case) and normalizes the media type to lower case (accepting the mixed-case
one). Propagate its parse error, and add malformed-parameter and mixed-case
cases to TestValidateNDJSON.

Signed-off-by: hharshhsaini <sainiharsh3311@gmail.com>
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.84%. Comparing base (6a13ef0) to head (f0c3346).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9111      +/-   ##
==========================================
- Coverage   97.84%   97.84%   -0.01%     
==========================================
  Files         377      377              
  Lines       19812    19833      +21     
==========================================
+ Hits        19386    19405      +19     
- Misses        289      291       +2     
  Partials      137      137              
Flag Coverage Δ
badger_direct 7.77% <ø> (ø)
badger_e2e 11.91% <ø> (ø)
cassandra-4.x-direct-manual 12.72% <ø> (ø)
cassandra-4.x-e2e-auto 18.00% <ø> (ø)
cassandra-4.x-e2e-manual 18.00% <ø> (ø)
cassandra-5.x-direct-manual 12.72% <ø> (ø)
cassandra-5.x-e2e-auto 18.00% <ø> (ø)
cassandra-5.x-e2e-manual 18.00% <ø> (ø)
clickhouse-direct 7.85% <ø> (ø)
clickhouse-e2e 13.22% <ø> (-0.10%) ⬇️
elasticsearch-7.x-direct 21.35% <ø> (ø)
elasticsearch-8.x-direct 21.38% <ø> (ø)
elasticsearch-8.x-e2e 19.48% <ø> (-0.16%) ⬇️
elasticsearch-9.x-direct 21.38% <ø> (ø)
elasticsearch-9.x-e2e 19.70% <ø> (ø)
grpc_direct 6.86% <ø> (ø)
grpc_e2e 12.43% <ø> (ø)
kafka-3.x-v2 20.07% <ø> (-0.04%) ⬇️
memory_v2 14.57% <ø> (-0.02%) ⬇️
opensearch-1.x-direct 21.37% <ø> (ø)
opensearch-2.x-direct 21.37% <ø> (ø)
opensearch-2.x-e2e 19.48% <ø> (ø)
opensearch-3.x-direct 21.37% <ø> (ø)
opensearch-3.x-e2e 19.63% <ø> (+0.15%) ⬆️
query 12.38% <ø> (ø)
tailsampling-processor 6.83% <ø> (ø)
unittests 96.57% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkowall jkowall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed on c60ee14. Both requested changes are correctly addressed. Focused tests and the full fmt, lint, and test suite pass; the remaining Elasticsearch 9.x direct CI failure is unrelated to this test-only change.

@hharshhsaini

Copy link
Copy Markdown
Contributor Author

@jkowall Thanks for the re-review. As you noted, the only remaining red (elasticsearch 9.x direct) is unrelated to this test-only change, and the codecov / Coverage Gate reds are downstream of codecov not reporting. Whenever you have a moment, could you re-run those jobs or merge through, happy to rebase again if it helps.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

CI Summary Report

Metrics Comparison

⚠️ 4 metric change(s) detected (informational)

View changed metrics

For label-level diff details, open the CI run and expand the "Compare metrics and generate summary" step logs.

metrics_snapshot_kafka_v2⬇️ download diff
4 modified

  • otelcol_kafka_exporter_bytes
  • otelcol_kafka_exporter_bytes_uncompressed
  • otelcol_kafka_exporter_messages
  • otelcol_kafka_exporter_records

Code Coverage

✅ Coverage 98.4% (baseline 98.4%)

➡️ View CI run | View publish logs
2026-08-01 17:00:27 UTC

@jkowall
jkowall added this pull request to the merge queue Aug 1, 2026
Merged via the queue into jaegertracing:main with commit 835d71b Aug 1, 2026
200 of 204 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/storage changelog:test Change that's adding missing tests or correcting existing tests storage/elasticsearch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

snapshottest does not validate spec-conformant NDJSON for _bulk request bodies

2 participants