Skip to content

test(api): cover the new dashboard table queries (DataSpaceBackend#254) - #202

Draft
saqibmanan wants to merge 1 commit into
CIfrom
test-sync/DataSpaceBackend-pr254
Draft

saqibmanan wants to merge 1 commit into
CIfrom
test-sync/DataSpaceBackend-pr254

Conversation

@saqibmanan

@saqibmanan saqibmanan commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Covers CivicDataLab/DataSpaceBackend#254 (merge commit 3143e405), merged 2026-10-22T15:10:45Z.

§0 — merged is not deployed? Checked, it is

GET https://dev.api.civicdataspace.in/health/ currently answers git_sha: 3143e40564d4f381dffef764135ab1fd7b691b2c — exactly #254's own merge commit. Confirmed live on dev, no rollback.

What actually changed (from the diff, not the title)

Four new GraphQL queries — useCasesTable, collaborativesTable, aiModelsTable, publicationsTable — built the same way as the existing datasetsTable (#215): filters, sort options, pagination, and a statusCounts field for tab labels. All four are additive next to their existing plain queries (useCases, collaboratives, aiModels, publications), which are untouched. The PR's second commit also refactors datasetsTable's inline status-counting into a shared status_counts() helper (api/utils/qs_utils.py), now used by all five table queries.

Confirmed directly against the diff (api/schema/{usecase,collaborative,aimodel,publication}_schema.py, api/utils/qs_utils.py), not the PR title/description.

Category chosen: api + smoke (+ functional/regression per test, same split as test_api_013_datasets_table.py)

Pure GraphQL schema/resolver change, no UI surface. Mirrors the exact category split the sibling datasetsTable file already uses in this repo.

Existing coverage re-run first (§3: check the changed paths' existing tests)

Because this PR touches shared helper code, I re-ran the existing test_api_013_datasets_table.py (unchanged) against dev before writing anything new:

tests/api/smoke/test_api_013_datasets_table.py::test_anon_include_public_returns_published_datasets_only PASSED
tests/api/smoke/test_api_013_datasets_table.py::test_anon_without_include_public_sees_nothing PASSED
tests/api/smoke/test_api_013_datasets_table.py::test_title_icontains_filter_narrows_results PASSED
tests/api/smoke/test_api_013_datasets_table.py::test_filtering_on_an_unknown_field_is_a_graphql_error PASSED
tests/api/smoke/test_api_013_datasets_table.py::test_rows_carry_a_resource_count PASSED
tests/api/smoke/test_api_013_datasets_table.py::test_status_counts_ignore_the_status_filter_itself PASSED
6 passed in 5.61s

The status_counts() extraction didn't regress the existing query.

Test added

tests/api/smoke/test_api_029_listing_tables.py, reusing anon_graphql_client (no new fixtures):

  • Visibility, useCasesTable/collaborativesTable: anon+includePublic statusCounts is {"PUBLISHED"} only. A real finding while writing this: unlike datasetsTable, omitting includePublic does not return zero rows here — _visible_use_cases/_visible_collaboratives already fall back to the PUBLISHED-only queryset for an anonymous caller regardless of the flag (the PR body says this explicitly: "This differs from datasetsTable, which returns nothing here"). My first draft assumed the datasetsTable shape and asserted 0 rows — it failed on real data (132/106 rows came back), so I rewrote it to assert the actual, intended invariant: includePublic must not change anon's result at all. Live before/after check is in the test's docstring.
  • aiModelsTable deliberately excluded from the "statusCounts is one status" check: its public set is is_public=True, is_active=True (boolean flags), not the status enum, so live data on dev legitimately shows both ACTIVE and REGISTERED rows as "public". Asserting single-status here would have been a test written against a wrong assumption, not the real contract — confirmed on live data (105 ACTIVE + 24 REGISTERED) before deciding not to write it.
  • Filtering narrows, three queries: title/display_name icontains filters checked to produce 0 < filtered < total on live data (use cases: "testing" → 1 of 132; collaboratives: "test collab" → 10 of 106; AI models: "New AI Model" → 114 of 129) — not <=, per SKILL.md §5's "flipping is not enough" guidance.
  • Allowlist is field-name-exact: aiModelsTable accepts display_name (the model field) and rejects the GraphQL-cased displayName, naming the allowed fields — same allowlist mechanism test_api_013's unknown-field test guards, checked bidirectionally on live data.
  • statusCounts ignores the status filter: same shape as test_api_013's equivalent, filtering on DRAFT (not PUBLISHED, which is a no-op for an anon caller and would have been a vacuous check per SKILL.md §5).
  • publicationsTable — documented gap, not faked coverage: dev currently has zero published Publications ({ publications { id } } → []), so there's no live data to assert a visibility or filtering invariant against. The one test here only checks the query doesn't error and returns a consistent empty shape; it's explicitly marked in its docstring as not real regression coverage, with instructions to extend it once a Publication exists on dev.

Gating: deployed_pr, not plain readonly

#254 is not yet on main (verified: git merge-base --is-ancestor 3143e40564d4f381dffef764135ab1fd7b691b2c origin/main fails in DataSpaceBackend). readonly + deployed_pr("DataSpaceBackend#254"), same pattern as #250/#223/#245/#215.

Proof (red → green), pasted below

Collection, under both of CI's real marker filters:

$ pytest tests/api/smoke -m "smoke" --collect-only -q         # pull_request filter
59/137 tests collected (78 deselected)

$ pytest tests/api -m "smoke or functional or regression" --collect-only -q   # dispatch "full" filter
131/143 tests collected (12 deselected)

Green — against dev, all 10 new tests + the 6 existing datasetsTable tests:

16 passed in 11.98s

Correctly skips against prod (marker in place):

SKIPPED [6] ...: deployed_pr DataSpaceBackend#254: https://api.datakeep.civicdays.in runs 5c664716, which doesn't include 3143e405 yet

(6 skip lines — one per distinct call site; parametrized tests share a line.)

Red — flipped assertions, both reverted before commit, never pushed:

  1. statuses == {"PUBLISHED"} → !=, and the includePublic-no-op equality → !=:
FAILED [...sees_only_published_status[useCasesTable]]
  AssertionError: ... got {'PUBLISHED'} -- assert {'PUBLISHED'} != {'PUBLISHED'}
FAILED [...sees_only_published_status[collaborativesTable]] (same shape)
FAILED [...still_sees_published_rows[useCasesTable]]
  AssertionError: includePublic changed anon's result (132 vs 132) -- assert 132 != 132
FAILED [...still_sees_published_rows[collaborativesTable]] (same shape, 106 vs 106)
4 failed, 6 deselected
  1. Stronger proof for the narrowing-filter tests, per SKILL.md §5's "flipping is not enough": simulated what the assertion would see if the backend silently ignored the filters: argument (reused the unfiltered total as the "filtered" result, exactly what an ignored-filter resolver would produce), run standalone (not shipped):
RED (as expected) -> filtering use_cases on 'testing' should narrow 132 rows, got 132

Confirms the assertion (0 < filtered < total, never <=) genuinely depends on the filter being applied.

All failures land at the intended assertion, not a setup/fixture error.

Also assessed in this run, no tests added

  • DataSpaceBackend#250 (merged earlier the same day) — covered separately in its own PR.

Gaps

  • publicationsTable has no real invariant coverage — see above. Extend test_publications_table_gap_no_published_rows_on_dev (or add siblings next to it, same pattern as the use-case/collaborative tests) once a Publication is published on dev.
  • Sorting (sortOptions) isn't covered for any of the four queries — out of scope for this pass; datasetsTable's own test file doesn't cover it either.
  • Authenticated visibility branches (organization-scoped, own-rows-only, superuser) — UNVERIFIED — not executed, no credentials in this environment. A human with TEST_EMAIL_1/TEST_PASSWORD_1 (and ideally an org-scoped account) should extend this file to cover those branches, same way test_api_013's org-visibility cases would need to be.

Housekeeping

  • Branch test-sync/DataSpaceBackend-pr254, based on origin/CI.
  • Duplicate check (SKILL.md §8) run clean: no existing test-sync/DataSpaceBackend-pr254 branch, no test-repo PR mentioning DataSpaceBackend#254.
  • File numbering: test_api_029_* (free on origin/CI; a sibling in-flight PR from this same run claimed 028 for DataSpaceBackend#249, pending).
  • Draft only — not requesting review, not merging.

Run context: scheduled cloud routine, no test credentials in this environment. Everything above except the authenticated-visibility gap was actually executed (unauthenticated, read-only).

Covers useCasesTable, collaborativesTable and aiModelsTable (the new
filterable/sortable/paginated listing queries, built the same way as the
existing datasetsTable) with the anon visibility boundary, a title filter
that genuinely narrows results, and a statusCounts-ignores-the-status-
filter check that avoids the vacuous PUBLISHED-filter case. Also re-ran
the existing datasetsTable coverage against dev to confirm #254's shared
status_counts() refactor didn't regress it. publicationsTable is left
with only a shape check -- dev has zero published Publications to assert
a real invariant against -- documented as a gap in the file and PR body.
Gated with deployed_pr("DataSpaceBackend#254") since the merge hasn't
reached main/prod yet.

Copy link
Copy Markdown
Contributor Author

consumer-smoke is red on this PR, but not because of this PR — the only change here is one new file under tests/api/smoke/, and consumer-smoke doesn't collect tests/api.

The failure is tests/consumer/smoke/test_components.py::test_TC_SEC_03_sort_dropdown, a pre-existing test this branch doesn't touch: '//div[contains(@class, 'sort') or .//select or .//button[contains(., 'Sort')]]' not satisfied by visibility_of_element_located. It failed at that exact assertion on every attempt including pytest's own built-in reruns, and the identical failure also reproduced on sibling PRs #200 and #201 from this same run, which touch entirely different files — so this isn't contention/flakiness, it's either the sort-dropdown locator or the live dev homepage no longer matching it.

No fix exists yet for this that I can port in. Leaving this for a human to look at test_components.py's sort-dropdown locator against the current dev homepage; already flagged once on #200, not repeating the investigation per PR.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

provider-smoke is also red on this PR, same cause already diagnosed on sibling PRs #200/#201: this PR only adds one file under tests/api/smoke/ and doesn't touch anything provider-smoke collects. The failures are post-login TimeoutExceptions waiting on data-dependent elements — the documented backend-rate-limit hang pattern in SKILL.md, from three sibling PRs (#200, #201, #202) each firing api-smoke+consumer-smoke+provider-smoke back to back in the same ~20 minute window.

Not re-running: same fixed-window reasoning as #200/#201 — an immediate retry would likely just hit the same throttling. Leaving this for a later CI run or a human once the window clears.


Generated by Claude Code

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