Skip to content

feat: serve response headers and content type declared in the spec - #127

Open
MaxMichel2 wants to merge 1 commit into
feat/networkmock-spec-reloadfrom
feat/networkmock-response-headers
Open

MaxMichel2 wants to merge 1 commit into
feat/networkmock-spec-reloadfrom
feat/networkmock-response-headers

Conversation

@MaxMichel2

Copy link
Copy Markdown
Collaborator

Summary

Closes #87. Also closes gap 7 of #91 (no assertion on response headers/content-type in NetworkMockPluginTest.kt).

Stacked on #124 (PR chain: #123 → #124 → this) — merge in order.

Mocked responses always carried a single hardcoded Content-Type: application/json header, with no way to declare additional headers per response. Worse, the parser already discarded the spec's own declared media type (the key under responses.<code>.content) while walking down to the example — so even a spec explicitly describing text/plain or a vendor JSON type was silently forced to application/json.

What changed

  • OpenApiDocument: new HeaderObject DTO, ResponseObject.headers, ComponentsObject.headers (for $ref targets).
  • OpenApiParser: resolveResponseIndex now threads the media-type key (previously discarded) and resolves declared headers — a header's literal example value, the same pattern query-parameter matching already uses — into a new ResolvedResponse record (path, contentType, headers), replacing the bare file-path string the response index used to carry. Header $refs resolve against components.headers, reusing the existing one-level-deep $ref resolution.
  • MockResponse: gains contentType (default "application/json") and headers (default empty) properties, threaded through MockConfigRepository.loadResponseFromPath.
  • NetworkMockPlugin: createMockHttpClientCall now builds the response's Headers via a HeadersBuilder — declared headers merge over the Content-Type default rather than replacing it, and an explicit Content-Type entry under the spec's own headers wins over the media-type-derived default.

Public API

Additive/defaulted — no existing call site needed updating. devview-networkmock-core/api/api.txt regenerated via metalavaGenerateSignature.

Tests

  • MockConfigRepositoryTest: content-type/headers threading, a $ref'd header resolving via components.headers, and the existing loadMockResponse test extended to assert the application/json default.
  • NetworkMockPluginTest: two new tests — Content-Type present by default (closes test: cover the gaps left by the audit #91 gap 7), and declared headers + a non-default content type reaching the response the app actually receives.

Docs: networkmock-core.md (new "Response headers and content type" section), networkmock-ktor.md (interception summary), devview-networkmock-ktor/CLAUDE.md (corrected the stale "empty headers" claim).

Verification

.\gradlew.bat detektFull -Pandroidx.baselineprofile.skipgeneration
.\gradlew.bat cleanTestAndroidHostTest testAndroidHostTest -Pandroidx.baselineprofile.skipgeneration
.\gradlew.bat :konsist:test -Pandroidx.baselineprofile.skipgeneration
.\gradlew.bat :sample:androidApp:assembleDebug -Pandroidx.baselineprofile.skipgeneration

All green, including a full repo-wide testAndroidHostTest run to confirm no fallout outside the networkmock modules.

🤖 Generated with Claude Code

Mocked responses always carried a single hardcoded Content-Type:
application/json header, with no way to declare additional headers
per response - and even the spec's own declared media type
(the key under responses.<code>.content) was discarded during
parsing.

- OpenApiDocument: HeaderObject DTO, ResponseObject.headers.
- OpenApiParser.resolveResponseIndex now threads the media-type key
  and resolves declared headers (a header's literal example value,
  same pattern as query parameter matching) into a new ResolvedResponse
  record, replacing the bare file-path string the response index used
  to carry. Header $refs resolve against components.headers.
- MockResponse gains contentType (default "application/json") and
  headers (default empty) properties, threaded through
  MockConfigRepository and into the Ktor plugin's synthetic
  HttpResponseData via a HeadersBuilder - declared headers merge over
  the Content-Type default, and an explicit Content-Type header in the
  spec wins over the media-type-derived one.

New public API on MockResponse is additive/defaulted - no existing
call site needed updating. api.txt regenerated.

Closes #87. Also closes gap 7 of #91 (no assertion on response
headers/content-type in NetworkMockPluginTest.kt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@MaxMichel2 MaxMichel2 added this to the 0.2.0 milestone Sep 22, 2026
@MaxMichel2 MaxMichel2 self-assigned this Sep 22, 2026
@MaxMichel2
MaxMichel2 added this pull request to stack #130 September 22, 2026 12:07
MaxMichel2 added a commit that referenced this pull request Sep 23, 2026
Re-scoped issue #91's gap list against current code before writing anything
(the issue predates the OpenAPI migration and named files/classes that no
longer exist) and found four of its seven gaps already closed by prior work:

- Delay precedence: covered except the "neither declares one -> null" case -
  extended with one more test (existing test already covers operation-level
  override and spec-default fallback).
- Ambiguous host-match precedence: already fully covered by
  MockConfigRepositoryTest's two "hosts collide" tests. No action.
- Preview/diff bottom sheet: already fully covered by
  MockResponsePreviewPageTest.kt (the page) and DiffLineUtilsTest.kt (the
  diff logic it drives). No action.
- Response Content-Type/header assertions: already closed by #127
  (returnsMockResponse_withDefaultContentTypeHeader /
  _withDeclaredHeadersAndContentType). No action.

Three gaps remained genuinely open:

- Real sample spec parsing: no test previously loaded the actual shipped
  sample specs (sample/network's sample-api.json, jsonplaceholder.json)
  through the real MockConfigRepository - every existing test uses hand-built
  inline fixtures, so the shipped sample could silently drift out of sync
  with what the parser accepts. New RealSampleSpecTest (androidHostTest,
  since java.io.File isn't available on Kotlin/Native and this is a plain JVM
  sanity check) reads them directly off disk via a Gradle-configured
  devview.sampleNetworkResourcesDir system property, computed once at
  configuration time from rootProject.file(...) so the test doesn't depend on
  the JVM working directory - deliberately not a compile-time dependency on
  the sample module, which would invert this module's place in the
  dependency graph.
- Query-parameter matching end-to-end through the Ktor plugin:
  RequestMatcherTest already covered matchesQueryParams in isolation, but
  NetworkMockPluginTest had no end-to-end coverage through the actual
  interception path. KtorPluginTestData's shared spec gains a listUsers
  operation (?type=user, mirroring the real sample spec's own listUsers) and
  NetworkMockPluginTest gains a "Query parameter matching" region: matches on
  the declared value, falls through to network on a different value or a
  missing param.
- Sticky-header status-family grouping: OperationPickerPage's response list
  already groups by StatusCodeFamily with a sticky header per group, but
  nothing asserted the header text itself or that responses actually land in
  distinct groups - NetworkMockOperationSheetTest's existing 200/404 fixture
  already spans two families, so one added test closes this by asserting
  both family headers render.

CHANGELOG.md documents this as a coverage-closing entry rather than a
user-facing change (nothing here alters library behavior).
MaxMichel2 added a commit that referenced this pull request Sep 23, 2026
Re-scoped issue #91's gap list against current code before writing anything
(the issue predates the OpenAPI migration and named files/classes that no
longer exist) and found four of its seven gaps already closed by prior work:

- Delay precedence: covered except the "neither declares one -> null" case -
  extended with one more test (existing test already covers operation-level
  override and spec-default fallback).
- Ambiguous host-match precedence: already fully covered by
  MockConfigRepositoryTest's two "hosts collide" tests. No action.
- Preview/diff bottom sheet: already fully covered by
  MockResponsePreviewPageTest.kt (the page) and DiffLineUtilsTest.kt (the
  diff logic it drives). No action.
- Response Content-Type/header assertions: already closed by #127
  (returnsMockResponse_withDefaultContentTypeHeader /
  _withDeclaredHeadersAndContentType). No action.

Three gaps remained genuinely open:

- Real sample spec parsing: no test previously loaded the actual shipped
  sample specs (sample/network's sample-api.json, jsonplaceholder.json)
  through the real MockConfigRepository - every existing test uses hand-built
  inline fixtures, so the shipped sample could silently drift out of sync
  with what the parser accepts. New RealSampleSpecTest (androidHostTest,
  since java.io.File isn't available on Kotlin/Native and this is a plain JVM
  sanity check) reads them directly off disk via a Gradle-configured
  devview.sampleNetworkResourcesDir system property, computed once at
  configuration time from rootProject.file(...) so the test doesn't depend on
  the JVM working directory - deliberately not a compile-time dependency on
  the sample module, which would invert this module's place in the
  dependency graph.
- Query-parameter matching end-to-end through the Ktor plugin:
  RequestMatcherTest already covered matchesQueryParams in isolation, but
  NetworkMockPluginTest had no end-to-end coverage through the actual
  interception path. KtorPluginTestData's shared spec gains a listUsers
  operation (?type=user, mirroring the real sample spec's own listUsers) and
  NetworkMockPluginTest gains a "Query parameter matching" region: matches on
  the declared value, falls through to network on a different value or a
  missing param.
- Sticky-header status-family grouping: OperationPickerPage's response list
  already groups by StatusCodeFamily with a sticky header per group, but
  nothing asserted the header text itself or that responses actually land in
  distinct groups - NetworkMockOperationSheetTest's existing 200/404 fixture
  already spans two families, so one added test closes this by asserting
  both family headers render.

CHANGELOG.md documents this as a coverage-closing entry rather than a
user-facing change (nothing here alters library behavior).
MaxMichel2 added a commit that referenced this pull request Sep 24, 2026
Re-scoped issue #91's gap list against current code before writing anything
(the issue predates the OpenAPI migration and named files/classes that no
longer exist) and found four of its seven gaps already closed by prior work:

- Delay precedence: covered except the "neither declares one -> null" case -
  extended with one more test (existing test already covers operation-level
  override and spec-default fallback).
- Ambiguous host-match precedence: already fully covered by
  MockConfigRepositoryTest's two "hosts collide" tests. No action.
- Preview/diff bottom sheet: already fully covered by
  MockResponsePreviewPageTest.kt (the page) and DiffLineUtilsTest.kt (the
  diff logic it drives). No action.
- Response Content-Type/header assertions: already closed by #127
  (returnsMockResponse_withDefaultContentTypeHeader /
  _withDeclaredHeadersAndContentType). No action.

Three gaps remained genuinely open:

- Real sample spec parsing: no test previously loaded the actual shipped
  sample specs (sample/network's sample-api.json, jsonplaceholder.json)
  through the real MockConfigRepository - every existing test uses hand-built
  inline fixtures, so the shipped sample could silently drift out of sync
  with what the parser accepts. New RealSampleSpecTest (androidHostTest,
  since java.io.File isn't available on Kotlin/Native and this is a plain JVM
  sanity check) reads them directly off disk via a Gradle-configured
  devview.sampleNetworkResourcesDir system property, computed once at
  configuration time from rootProject.file(...) so the test doesn't depend on
  the JVM working directory - deliberately not a compile-time dependency on
  the sample module, which would invert this module's place in the
  dependency graph.
- Query-parameter matching end-to-end through the Ktor plugin:
  RequestMatcherTest already covered matchesQueryParams in isolation, but
  NetworkMockPluginTest had no end-to-end coverage through the actual
  interception path. KtorPluginTestData's shared spec gains a listUsers
  operation (?type=user, mirroring the real sample spec's own listUsers) and
  NetworkMockPluginTest gains a "Query parameter matching" region: matches on
  the declared value, falls through to network on a different value or a
  missing param.
- Sticky-header status-family grouping: OperationPickerPage's response list
  already groups by StatusCodeFamily with a sticky header per group, but
  nothing asserted the header text itself or that responses actually land in
  distinct groups - NetworkMockOperationSheetTest's existing 200/404 fixture
  already spans two families, so one added test closes this by asserting
  both family headers render.

CHANGELOG.md documents this as a coverage-closing entry rather than a
user-facing change (nothing here alters library behavior).
MaxMichel2 added a commit that referenced this pull request Sep 24, 2026
Re-scoped issue #91's gap list against current code before writing anything
(the issue predates the OpenAPI migration and named files/classes that no
longer exist) and found four of its seven gaps already closed by prior work:

- Delay precedence: covered except the "neither declares one -> null" case -
  extended with one more test (existing test already covers operation-level
  override and spec-default fallback).
- Ambiguous host-match precedence: already fully covered by
  MockConfigRepositoryTest's two "hosts collide" tests. No action.
- Preview/diff bottom sheet: already fully covered by
  MockResponsePreviewPageTest.kt (the page) and DiffLineUtilsTest.kt (the
  diff logic it drives). No action.
- Response Content-Type/header assertions: already closed by #127
  (returnsMockResponse_withDefaultContentTypeHeader /
  _withDeclaredHeadersAndContentType). No action.

Three gaps remained genuinely open:

- Real sample spec parsing: no test previously loaded the actual shipped
  sample specs (sample/network's sample-api.json, jsonplaceholder.json)
  through the real MockConfigRepository - every existing test uses hand-built
  inline fixtures, so the shipped sample could silently drift out of sync
  with what the parser accepts. New RealSampleSpecTest (androidHostTest,
  since java.io.File isn't available on Kotlin/Native and this is a plain JVM
  sanity check) reads them directly off disk via a Gradle-configured
  devview.sampleNetworkResourcesDir system property, computed once at
  configuration time from rootProject.file(...) so the test doesn't depend on
  the JVM working directory - deliberately not a compile-time dependency on
  the sample module, which would invert this module's place in the
  dependency graph.
- Query-parameter matching end-to-end through the Ktor plugin:
  RequestMatcherTest already covered matchesQueryParams in isolation, but
  NetworkMockPluginTest had no end-to-end coverage through the actual
  interception path. KtorPluginTestData's shared spec gains a listUsers
  operation (?type=user, mirroring the real sample spec's own listUsers) and
  NetworkMockPluginTest gains a "Query parameter matching" region: matches on
  the declared value, falls through to network on a different value or a
  missing param.
- Sticky-header status-family grouping: OperationPickerPage's response list
  already groups by StatusCodeFamily with a sticky header per group, but
  nothing asserted the header text itself or that responses actually land in
  distinct groups - NetworkMockOperationSheetTest's existing 200/404 fixture
  already spans two families, so one added test closes this by asserting
  both family headers render.

CHANGELOG.md documents this as a coverage-closing entry rather than a
user-facing change (nothing here alters library behavior).

This branch has not been deployed

No deployments
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