Skip to content

[pending DataSpaceBackend#224] Add API coverage for external contributors privacy mask - #197

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

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

Conversation

@saqibmanan

@saqibmanan saqibmanan commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Pending: covers open CivicDataLab/DataSpaceBackend#224 (head 98581547). Skipped in CI until it merges. Safe to merge before it.

Source PR

CivicDataLab/DataSpaceBackend#224 — "Create external user for collaborative, publications, use-cases"

  • base: dev, open, created 2026-10-06 (>72h old, no coverage yet)
  • headRefOid: 985815476a819bae1b066a32896769130ed8a849

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

  • New ExternalContributor model (name, email, image, designation, bio, has_approved,
    timestamps), wired as an M2M onto Publication, UseCase and Collaborative.
  • New GraphQL type TypeExternalContributor enforces a privacy mask: every field except
    id/hasApproved/createdAt/updatedAt resolves to a safe default when
    has_approved is False — name() → "Anonymous", email()/image()/
    designation()/bio() → None.
  • New externalContributors list field added to TypeCollaborative, TypeUseCase and
    TypePublication, with no permission class — exposed through the same public,
    unauthenticated queries as the existing contributors field.
  • Collaborative.organization is removed, replaced by a plain designation: str
    field, and is_individual_collaborative is hardcoded to always return True
    (previously self.organization is None). This is a real behaviour change (every
    existing collaborative that had an org now reads as "individual") but is secondary to
    the ExternalContributor feature — see "Caveats" below, not covered by new tests here.

Confirmed directly against the PR diff in DataSpaceBackend (api/types/type_external_contributor.py,
api/types/type_collaborative.py, api/types/type_usecase.py, api/types/type_publication.py),
not from the PR title/description.

Category chosen: api + smoke

This is a pure GraphQL schema/resolver change with no UI surface, so api is the right
category. smoke is required in addition to api/pending_pr so CI's pull_request
marker filter (-m "smoke", from run-smoke.yml) actually selects it — an
api-only/regression-only test here would be silently deselected on every event
(this repo has no regression-gated job that ever runs; see SKILL.md §5). regression
was deliberately not used for the same reason.

Test added

tests/api/smoke/test_api_025_external_contributors.py — two unauthenticated GraphQL
tests (test_usecases_external_contributors_field_does_not_exist_yet,
test_collaboratives_external_contributors_field_does_not_exist_yet), reusing the
existing anon_graphql_client fixture from tests/api/conftest.py (no new fixtures or
Page Objects needed — there is no UI layer to this test). Each sends
{ useCases(pagination:{limit:1}) { id title externalContributors { id hasApproved name email designation bio } } } (and the collaboratives equivalent) and asserts the
response is GraphQL's "Cannot query field 'externalContributors'" error with data: null — not just "any error".

Both tests carry pytest.mark.pending_pr("DataSpaceBackend#224") (new file, so set via
pytestmark), plus api and smoke.

Red proof (today's dev schema doesn't have the field)

Captured directly with curl against https://dev.api.civicdataspace.in/api/graphql:

$ curl -sS -X POST https://dev.api.civicdataspace.in/api/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ useCases(pagination: {limit: 1}) { id title externalContributors { id hasApproved name email designation bio } } }"}'

{
  "data": null,
  "errors": [
    {
      "message": "Cannot query field 'externalContributors' on type 'TypeUseCase'. Did you mean 'contributors'?",
      "locations": [{"line": 1, "column": 47}]
    }
  ]
}

And the actual pytest run against dev, with the pending_pr marker temporarily removed
(API_BASE_URL=https://dev.api.civicdataspace.in):

tests/api/smoke/test_api_025_external_contributors.py::test_usecases_external_contributors_field_does_not_exist_yet PASSED [ 50%]
tests/api/smoke/test_api_025_external_contributors.py::test_collaboratives_external_contributors_field_does_not_exist_yet PASSED [100%]
2 passed in 2.17s

These "PASSED" because the assertion is "today's schema rejects this field" — that
rejection is the red proof that DataSpaceBackend#224 hasn't landed on dev yet. To check
the assertion is not vacuous, I inverted it locally (asserted data is not None,
i.e. the post-merge expectation) and reran the same test against dev — it failed as
expected:

FAILED tests/api/smoke/test_api_025_external_contributors.py::test_usecases_external_contributors_field_does_not_exist_yet
AssertionError: [INVERTED CHECK] Expected data (field exists), got: None
{'data': None, 'errors': [{'message': "Cannot query field 'externalContributors' on type 'TypeUseCase'. Did you mean 'contributors'?", ...}]}
1 failed in 2.14s

(That inversion was local-only, reverted before committing — never part of the shipped file.)

Collection + skip-gating proof

Collected (both files, both events' marker filters):

$ python -m pytest tests/api/smoke/test_api_025_external_contributors.py --collect-only -q
tests/api/smoke/test_api_025_external_contributors.py::test_usecases_external_contributors_field_does_not_exist_yet
tests/api/smoke/test_api_025_external_contributors.py::test_collaboratives_external_contributors_field_does_not_exist_yet
2 tests collected in 1.53s

$ python -m pytest tests/api/smoke -m "smoke" --collect-only -q | grep external_contributors
tests/api/smoke/test_api_025_external_contributors.py::test_usecases_external_contributors_field_does_not_exist_yet
tests/api/smoke/test_api_025_external_contributors.py::test_collaboratives_external_contributors_field_does_not_exist_yet
# 56/129 tests collected (73 deselected) — matches pull_request's "-m smoke" on tests/api/smoke

$ python -m pytest tests/api -m "smoke or functional" --collect-only -q | grep external_contributors
tests/api/smoke/test_api_025_external_contributors.py::test_usecases_external_contributors_field_does_not_exist_yet
tests/api/smoke/test_api_025_external_contributors.py::test_collaboratives_external_contributors_field_does_not_exist_yet
# 107/135 tests collected (28 deselected) — matches dispatch's "-m smoke or functional" on tests/api

Skip-gating round-trip, with the pending_pr marker present (as shipped), against dev:

$ API_BASE_URL=https://dev.api.civicdataspace.in python -m pytest tests/api/smoke/test_api_025_external_contributors.py -v -rs
tests/api/smoke/test_api_025_external_contributors.py::test_usecases_external_contributors_field_does_not_exist_yet SKIPPED [ 50%]
tests/api/smoke/test_api_025_external_contributors.py::test_collaboratives_external_contributors_field_does_not_exist_yet SKIPPED [100%]
SKIPPED [2] tests/api/smoke/test_api_025_external_contributors.py: pending_pr DataSpaceBackend#224: not merged yet
2 skipped in 1.26s

Exact reason string matches _pending_pr_state() in conftest.py
(f"pending_pr {ref}: not merged yet").

UNVERIFIED — green behaviour not executed anywhere

UNVERIFIED: PR not deployed anywhere — once DataSpaceBackend#224 is on dev, re-run this
file; the query should then succeed and (a) an unapproved contributor's name must read
"Anonymous" with email/designation/bio/image all null, (b) an approved one must show its
real values.
DataSpaceBackend cannot be served in this sandbox (no Postgres, Redis or
Keycloak available), so the privacy-mask logic itself (the actual point of
TypeExternalContributor) has not been exercised against running code — only the
schema-absence red proof above has. This test deliberately does not assert the green
behaviour; it only proves today's gap. A human (or a future CI run once #224 merges)
should rewrite/extend it to cover (a) and (b) once real ExternalContributor records
with both hasApproved states exist on dev.

Caveats / out of scope

  • Collaborative.organization → designation + is_individual_collaborative always
    True
    : this PR also removes the organization field from TypeCollaborative and
    hardcodes is_individual_collaborative to True regardless of whether the
    collaborative actually has an organization. That's a real behaviour change (existing
    org-backed collaboratives now read as "individual") but is secondary to the
    ExternalContributor feature and is not covered by new tests here — flagging it as
    a gap rather than guessing at the intended semantics.
  • Never readonly — per SKILL.md §9, a pending test is never marked readonly
    since the prod-deploy gate runs readonly tests and this would fail every prod deploy
    until #224 actually ships to prod. No follow-up needed unless/until a readonly
    variant is wanted after the feature is live everywhere.
  • No Page Object/locator layer needed or added — this is a pure unauthenticated
    GraphQL check with no UI surface.
  • File numbering: test_api_025_* — confirmed free on origin/CI
    (git ls-tree origin/CI --name-only tests/api/smoke/ had 001–024 and 027, nothing at
    025/026).

Housekeeping

  • Branch test-sync/DataSpaceBackend-pr224, based on origin/CI (confirmed via recent
    merged test PRs that CI, not main/dev, is where this repo's test work lands and
    where its workflows run).
  • Duplicate check (SKILL.md §8) run clean both before and immediately before pushing: no
    existing test-sync/DataSpaceBackend-pr224 branch, no PR mentioning
    DataSpaceBackend#224.
  • Draft only — not requesting review, not merging.

Covers the new externalContributors GraphQL field on TypeUseCase and
TypeCollaborative (DataSpaceBackend PR #224, open on dev). Querying the
field today fails with GraphQL's "Cannot query field" error, which is
the proof that the feature is not live yet. Gated with pending_pr so it
skips in CI until the source PR merges.
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