---
id: w6/m10/t008
title: "Test coverage: meaningful tests for enrichment, omit-on-unset degradation, and Team page rendering"
worker: worker6
status: done
estimate: 40m
depends_on: [w6/m10/t006]
---

## Objective

Standing closing task. Add tests asserting the real behavior m10 shipped and its failure modes — not tautologies: enrichment values are correct, degradation is graceful, and the three surfaces agree.

## Context

- `lego/backend/internal/members/members_test.go` — existing suite with fakes for store/granter/revoker; extend the store fake with `OwnerIDForSubject` and add an identity-reader fake.
- `dashboard/src/features/team/components/__tests__/team-panel.test.tsx`.

## Steps / target cases

1. `List` returns the own- id minted for each subject and the email the reader resolved.
2. Nil reader ⇒ empty email, request succeeds (and a lookup-miss for one member degrades only that member).
3. Store error from `OwnerIDForSubject` surfaces as a 5xx, not a silent blank.
4. Three-surface agreement (REST/GraphQL/MCP emit the same `userId`/`email` — extend the existing surface-parity pattern if one exists in the suite).
5. Team page: email-primary rendering + userId fallback + mutations still keyed correctly.

## Files

- `lego/backend/internal/members/members_test.go`
- `dashboard/src/features/team/components/__tests__/team-panel.test.tsx`

## Acceptance criteria

- [x] Each target case above has an assertion that fails if the behavior regresses.
- [x] `cd lego/backend && go test ./...` and the dashboard suite green.

## Coverage record (2026-07-12)

All five target cases have a dedicated, non-tautological assertion (added across t002/t003/t004, one added directly here):

1. **Own- id minted + correct email resolved** — `members_test.go` `TestListEnrichesUserIDAndEmail` (own- prefix, exact email match, id stable across two `List()` calls for the same subject).
2. **Nil reader ⇒ empty email, no error; a miss degrades only that member** — `TestListNilIdentitiesOmitsEmail` (no `Identities` wired) + `TestListEmailLookupMissDegradesOnlyThatMember` (two members, one resolves, one misses — asserts the miss doesn't blank the hit).
3. **Store error surfaces loudly** — `TestListOwnerIDStoreErrorSurfaces` (service-level: `List` returns a non-nil error) + the new `TestListOwnerIDStoreErrorIsHTTP5xx` (REST-level: asserts the response status is actually 5xx, not a silently-empty 200).
4. **Three-surface agreement** — `members_surface_parity_test.go` `TestThreeSurfaceParity_UserIDAndEmail` (real `RegisterREST`/`GraphQLQuery`/`RegisterMCP` fragments, same fixture, asserts identical `userId`/`email`).
5. **Team page: email-primary + userId fallback + mutations still correctly keyed** — `team-panel.test.tsx`: "a member with a resolvable email shows the email, not the subject", "a member without email falls back to the own- userId, never blank", "a member with neither email nor userId still shows the subject (fully degraded)", plus the existing remove-flow test extended to assert `removeMember` is still called with the raw `subject` (not `userId`) and the confirm dialog now interpolates the display identity.

`cd lego/backend && go build ./... && go vet ./... && go test ./...` — all green. `dashboard/`: `yarn typecheck` clean, `yarn vitest run src/features/team` (10/10) and the full suite (654/654) green.

## Out of scope

- Coverage-gaming (snapshot-everything, tautological asserts).
