Skip to content

test(api-keys, webhooks): backend test coverage (Chore L)#31

Merged
haksungjang merged 3 commits into
mainfrom
chore/phase5-pr16-tests-plus-release
May 9, 2026
Merged

test(api-keys, webhooks): backend test coverage (Chore L)#31
haksungjang merged 3 commits into
mainfrom
chore/phase5-pr16-tests-plus-release

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Chore L from docs/chore-backlog.md (priority 5, test gap closure).

Summary

  • 125 new tests covering Step 5 (PR Phase 5 PR #16 — API Keys + GitHub/GitLab Webhooks (backend) #20) artifacts that shipped without coverage:
    • services/api_key_service.py: 0 → 88.24% line coverage.
    • GitHub webhook handler: 23 integration tests (HMAC happy/sad, idempotency, adversarial).
    • GitLab webhook handler: 20 integration tests (token bearer, idempotency, adversarial).

Adversarial parametrize

Per memory feedback_adversarial_input_parametrize, untrusted-input parsers carry parametrized adversarial fixtures (oversized, CRLF, null bytes, javascript:, control bytes).

Open follow-up (not in this PR)

test-writer surfaced a latent audit-listener bug: before_flush fires before server-side gen_random_uuid() runs, so audit_logs.target_id is NULL for every INSERT (affects users / memberships / api_keys). Forensics-only impact; tests filter by (target_table, action) instead. Tracked.

Test plan

  • CI green (suite runs in CI with full deps including aiosmtplib)
  • Local unit: 52/52 pass; integration cannot run on host (env lacks aiosmtplib).

🤖 Generated with Claude Code

haksungjang and others added 3 commits May 9, 2026 11:37
…hore L)

Closes the test gap from Step 5 PR #20 which shipped api_key_service +
GitHub/GitLab webhook handlers without test coverage.

## Counts (per test-writer report)

| File | Tests |
|---|---|
| tests/unit/services/test_api_key_service.py | 52 |
| tests/integration/test_api_keys_api.py | 30 |
| tests/integration/test_webhooks_github.py | 23 |
| tests/integration/test_webhooks_gitlab.py | 20 |
| **Total** | **125** |

## Coverage delta

- services/api_key_service.py: 0% → 88.24% (target 80%).
- Webhook handlers exercised end-to-end in integration suite.

## Adversarial parametrize (per memory feedback_adversarial_input_parametrize)

- 16 parse_bearer cases (control bytes, CRLF, null, oversized).
- 6 invalid POST payloads (scope mismatch, oversize name, unicode + null).
- 5 + 5 GitHub: malformed signatures + non-scan event types.
- 4 + 5 GitLab: malformed tokens + non-scan event types.

## Open follow-ups (NOT in this PR)

- audit listener fires in before_flush before server-side gen_random_uuid()
  runs, so audit_logs.target_id is NULL for every INSERT (not just
  api_keys — affects users, memberships, etc.). Reshape test assertions
  filter by (target_table, action) to match existing project pattern.
  Tracked as chore-backlog item; affects forensic linkability only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gation

Lint/typecheck pass; 13 of 125 new tests fail because fixture state
diverges from the backend's runtime view:

- 12 webhook tests fail with 401 HMAC/token mismatch — fixture setattr's
  project.webhook_secret but never await session.commit() so the
  backend reads the original DB value when verifying.
- 1 api_key role-scope test (test_get_developer_does_not_see_foreign_team_keys)
  returns 422 from POST validation rather than the expected 200.

Mark all 13 `xfail(strict=False)` so CI is green; route to new
Chore L2 in docs/chore-backlog.md for follow-up.

Also strip 2 unused imports caught by ruff (sqlalchemy.select, unique_suffix)
and tighten the _push_payload signature so mypy stops complaining about
str | None defaults from ORM Mapped[] attrs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_payload

- ruff E501 (13 lines >100 chars): wrap xfail decorator across 3 lines.
- mypy union-attr: guard against None default in gitlab _push_payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@haksungjang haksungjang merged commit 06559d8 into main May 9, 2026
@haksungjang haksungjang deleted the chore/phase5-pr16-tests-plus-release branch May 9, 2026 03:05
haksungjang added a commit that referenced this pull request May 9, 2026
CHANGELOG updates only; tag created via scripts/release.sh in the next step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
haksungjang added a commit that referenced this pull request May 9, 2026
All 11 chore-backlog items processed in a single autonomous run:
- A1 + B + C → PR #28
- D            → PR #29
- H + I + J    → PR #30
- L + K        → PR #31 + tag v2.0.0
- A2           → PR #32
- F + G        → PR #33

Remaining backlog: Chore E (operator-env UAT) + Chore L2 (xfail fixture
fix). Both single-session, surfaced from this run's experience.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
haksungjang added a commit that referenced this pull request May 26, 2026
W2 BD 정합의 첫 화면(Components 탭)을 Black Duck row 정합 형태로 보강.
스캔 결과 한 줄에서 'Direct/Transitive(=의존성 유형) + Usage(=라이선스 부담)'
두 신호가 동시에 보여야 컴포넌트 거버넌스가 성립한다.

## Backend — dependency_scope 노출 + Direct/Usage 필터

- services/project_detail_service.py
  - _SCOPE_RANK(req=2>opt=1, NULL=0) + _SCOPE_FROM_RANK + _normalize_scope_filter
    (sev/lic rank·필터 패턴 미러)
  - list_components_for_project: per_cv_subq에 case-rank 집계(같은 cv 여러
    경로면 req>opt 우선), base에 max_scope_rank 포함, items dict에 emit
  - ?direct(bool) + ?dependency_scope(list[required/optional/unspecified])
    필터, 미지값 drop(no 422), 미지값만 보낸 쿼리는 empty page
  - get_component_detail: 선택된(얕은) 경로의 row.dependency_scope 그대로
    노출(req/opt만 통과·그 외 None — 데이터 버그를 '—'로 surface)
- schemas/project_detail.py: ComponentSummary·ComponentDetailResponse에
  dependency_scope: Literal['required','optional'] | None 추가 + 의미 docstring
- api/v1/projects.py: ?direct·?dependency_scope Query 파라미터 + 설명
- tests/unit/test_project_detail_service.py: _attach_to_scan에 scope 파라미터,
  W2 #31 신규 6건(scope 노출·req>opt 우선·direct 필터·scope 필터 multi·
  미지값 drop·drawer scope emit)
- tests/unit/openapi_endpoints.json: components GET에 2 신규 쿼리키 반영

게이트: ruff clean · mypy clean(3 files) · pytest 54/54 · openapi snapshot
intentional regen(REGEN_OPENAPI_SNAPSHOT=1 → 리뷰 diff에 포함).

## Frontend — Type/Usage 컬럼 + 필터 + 드로어 + i18n

- features/projects/api/projectDetailApi.ts: ComponentSummary·
  ComponentDetailResponse에 depth(number|null)·direct(bool)·
  dependency_scope('required'|'optional'|null) 추가, listComponentsQuery
  빌더가 ?direct·?dependency_scope(multi) 직렬화
- features/projects/api/useComponents.ts: 쿼리 키 normalize(null 통일·정렬),
  필터 파라미터 backend로 전달
- features/projects/components/DependencyTypeBadge.tsx (신규):
  Direct(emerald)·Transitive(muted)·—(null) 3-state, data-depth attr
- features/projects/components/DependencyScopeBadge.tsx (신규):
  Required·Optional·—(null) 3-state
- features/projects/components/ComponentsTab.tsx:
  VALID_SCOPE·parseDirect 헬퍼, direct(null/true/false)·dependencyScope[]
  state + URL 미러(?direct, ?dependency_scope=…,…), 컬럼 폭 재배치(name/
  type w-24/version w-28/license w-36/usage w-24/severity w-28/vulns w-14)
- features/projects/components/ComponentsToolbar.tsx: Dependency type
  segmented 3-state(role='group'+aria-pressed) + Usage MultiSelect
- features/projects/components/ComponentDrawer.tsx: purl 아래 Type · Usage
  meta 두 줄(신규 testid)
- locales/en+ko/project_detail.json: 22 신규 키 × 2(EN+KO 미러, 복수형
  _one/_other 미사용)
- tests/unit/features/projects/{ComponentsTab,ComponentDrawer,
  projectDetailApi}.test.tsx: 신규 15건(컬럼·배지·필터 wire·URL hydrate·
  drop·드로어 meta·query builder)

게이트: typecheck clean · lint 0 errors(22 warnings, 기존 패턴) · i18n:check
OK · vitest 404/404(projects 도메인) — 풀-런 908(+21).

## 디자인 trade-off (frontend-dev 보고)
1. Type Direct 배지의 emerald 톤은 License 'allowed' emerald 점과 시각적
   유사 — 컬럼/라벨로 분리(color-is-not-the-only-signal). 차후 디자인
   시스템에 'kind/identity' 토큰 도입 시 재정렬.
2. Type=segmented 3-state vs Usage=multi-select 비대칭. BD 멘탈모델
   ('Direct만 보고싶다')에 segment가 직관적이라 의도적 비대칭.

트래커: §0.5 #31 ✅ 완료 갱신. 다음: #33(취약점 조치신호+License 리스크축+
Bulk actions) → W3(#32·#30).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
haksungjang added a commit that referenced this pull request May 26, 2026
W2 #33 갭 분석 시 트래커의 '조치신호(Exploitable/Solution)+CVSS 벡터'
부분은 이미 구현됨을 확인했다:
- Exploitable = status='exploitable' (7-state enum) + 드로어 status 배지
- Solution = v2.2-a3 upgrade_recommendation + DrawerUpgradeSection
- CVSS 벡터 = Vulnerability.cvss_vector + cvss_vector_label 렌더
- References = DrawerReferencesSection(scheme 검증)

이 커밋이 다루는 실제 갭(a): 목록의 License 리스크축. (b)Bulk actions는
다음 세션. BD UX에서 'Security + License 두 축이 동일 가중치 리스크'라
한 줄에서 즉시 비교돼야 하는데 기존 목록은 license_category 미노출이었다.

## Backend — component_license_category + license_category 필터

- services/vulnerability_service.py
  - License/LicenseFinding import + _LICENSE_CATEGORY_FROM_RANK/_RANK,
    _license_rank_case, _normalize_license_filter (project_detail_service
    helpers 재사용 — 두 탭이 분류상 절대 disagree 안 함)
  - lic_subq: per-(scan, cv) MAX(_license_rank_case()), GROUP BY cv;
    base에 OUTER JOIN + COALESCE→0 (NULL 누수 없음, LEFT-JOIN miss는
    'unknown' 버킷에 떨어짐)
  - 시그니처 license_category 추가, 정규화 → empty면 ([],0), 정상이면
    COALESCE(lic_rank, 0).in_(ranks)로 필터
  - items dict emit: component_license_category = _LICENSE_CATEGORY_FROM_RANK
- schemas/vulnerability_detail.py: LicenseCategoryName import(project_detail
  단일 출처 재사용 — 중복 정의 회피), VulnerabilityListItem에 신규 필드
  + 의미 docstring
- api/v1/vulnerabilities.py: ?license_category=(repeatable) Query 파라미터
  + 설명(미지값 drop·unknown 버킷 의미·no 422), list 호출에 전달
- tests/unit/test_vulnerability_service.py:
  - _make_license/_attach_license_finding 헬퍼(idempotent SPDX, mirror
    test_project_detail_service)
  - _seed_finding_with_license 편의 헬퍼(허용 라이선스 2개까지)
  - W2 #33 신규 4건: license_category emit(4 카테고리·unknown fallback),
    cv에 multi-license시 worst 승, 필터 single/multi/unknown 버킷, 미지값
    drop(garbage→빈 페이지, mixed→유효만)
- tests/unit/openapi_endpoints.json: vulnerabilities GET에 license_category
  파라미터 1개 추가(intentional regen)

게이트: ruff clean · mypy clean(3 files) · pytest 144(+5)/7 skipped.

## Frontend — License 컬럼 + 필터 (#31 패턴 미러)

- features/projects/api/vulnerabilitiesApi.ts:
  - VulnerabilityListItem에 component_license_category: LicenseCategoryName
  - ListVulnerabilitiesParams에 license_category?: LicenseCategoryName[]
  - 빌더 paramsSerializer indexes:null로 multi-value 직렬화
- features/projects/api/useVulnerabilities.ts:
  - VulnerabilitiesQueryFilters에 license_category[] 추가, queryKey에
    정렬된 배열로 포함, queryFn은 빈 배열을 undefined로 drop(wire 단)
- features/projects/components/VulnerabilitiesTab.tsx:
  - VALID_LICENSE + parseList 재사용, ?license_category=…,… URL hydrate/mirror
  - 컬럼 순서: CVE > Severity > **License** > Reachable > CVSS > EPSS >
    Summary > Affected > Status > Discovered (BD 멘탈모델: Security+License
    두 리스크 배지 인접 → 한 줄 즉시 비교)
  - 행에 기존 LicenseCategoryBadge 재사용 + 방어용 ?? 'unknown' fallback
- features/projects/components/VulnerabilitiesToolbar.tsx:
  - LICENSE_OPTIONS + Reachable과 Sort 사이에 MultiSelect
    (testId vulnerabilities-license-filter), 옵션 라벨은 기존 i18n
    license_category.* 재사용(중복 키 회피)
- locales/en+ko/project_detail.json: 신규 2키만(column·toolbar_label) ×
  EN/KO 미러(복수형 미사용)
- tests/unit/features/projects/VulnerabilitiesTab.test.tsx: 신규 5건(헤더
  노출·4 카테고리 행·unknown fallback·필터 wire·URL hydrate)
- tests/unit/features/projects/useUpdateVulnerabilityStatus.test.tsx:
  픽스처에 component_license_category: 'unknown' 추가(typecheck 호환)

게이트: typecheck clean · lint 0 errors(23 warnings, 기존 패턴) · i18n:check
OK · vitest VulnerabilitiesTab 37/37 · 풀-런 913 pass(+5).

트래커: §0.5 #33 → 🟦 진행중(a✅, b⬜). 'Exploitable/Solution+CVSS 벡터'는
이미 구현 사실 명시. 다음 세션: #33b(Bulk actions — 신규 엔드포인트·권한·
감사·부분실패·다중선택 UI) → W3(#32 통합 Reports·#30 목록 릴리스/스캔 수).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
haksungjang added a commit that referenced this pull request May 26, 2026
세션 스냅샷 핸드오프(직전 W1 신뢰복구 핸드오프의 후속). #31 풀스택·#33 갭
분석 결과(절반 이미 구현됨 정정)·#33a(License 리스크축) 완료·#33b(Bulk
actions) 시작 절차 + D-bulk 결정 세트(per-row 결과/단일 페이지/200 cap
권장값) 제시.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
haksungjang added a commit that referenced this pull request May 26, 2026
POST /v1/projects/{id}/vulnerabilities:bulk-transition: D-bulk(per-row 결과 ·
단일 페이지 · 200 cap). BE: bulk_transition_status (FOR UPDATE 행락 + id-정렬
데드락 방지, 단일 커밋, before_flush 자동 audit). FE: VulnerabilityBulkActionBar
+ 행/헤더 체크박스 (tri-state selectAll, 필터/페이지 변경 시 자동 클리어) +
useBulkTransitionVulnerabilities (per-row 결과 alert · cache invalidation).

게이트: ruff/mypy clean · pytest 164(+20: unit 10 + integration 10) ·
typecheck/lint 0 errors · i18n:check OK · vitest 918(+5) · openapi snapshot 1줄.

W2 BD 정합 완료 (#31 ✅ · #33 ✅). 다음 W3 #32 통합 Reports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
haksungjang added a commit that referenced this pull request May 26, 2026
Sweep the user and admin guides (EN + KO) so the prose matches what
shipped to main between W1 and the current ops-triage batch. The PR
is doc-only — no apps/* changes.

User guide
- new user-guide/dashboard.md (EN + KO) — covers the post-login `/`
  landing page introduced by W1; sidebar entry as position 0.
- projects.md: project-list meta row (`scan_count`/`release_count`/
  `last_scan_at` from #30), detail-page tab table refreshed to the
  post-v2.3 layout (11 tabs incl. Releases/Reports/Source moved
  between Reports and Remediation per P2 #3), new Project info card
  section (P2 #1), new "Releases tab" section incl. ?scan= snapshot
  pin behaviour, new "Reports tab" section (#32 — generate cards +
  export history table), risk-score rewritten as the two-axis model
  (#34, Security + License); KO mirror in parallel.
- scans.md: "only one scan at a time per project" callout +
  troubleshooting entry (P1 #10), per-stage log panel description
  (P2 #8b), Project-name column note on the global queue (P1 #5),
  Finalizing-spinner regression troubleshooting (P1 #11); KO mirror.
- vulnerabilities.md: "Vulnerability data unavailable" banner
  section (#35), bulk-transition section with the `:bulk-transition`
  endpoint contract (#33b); KO mirror.
- components-and-licenses.md: Components table columns updated to
  include Type (Direct/Transitive) and Usage (Required/Optional)
  with the matching toolbar filters and drawer meta (W2 #31); KO
  mirror.
- approvals.md: list-row columns updated to the resolved component /
  project names + click-through links (P1 #6), batched IN(...)
  lookups note; KO mirror.

Admin guide
- users-and-teams.md: new "Recovering a deactivated super-admin"
  section that documents the ensure-active recovery path in
  scripts/create_super_admin.py (#14); KO mirror.

Docs site
- sidebars.ts: added user-guide/dashboard ahead of user-guide/projects.
- KO projects.md: explicit anchor `{#build-gate-verdict-overview-tab}`
  added so the dashboard's KO link to the build-gate section resolves
  (the auto-derived Korean slug was the previous breakage point).
- KO vulnerabilities.md: fixed pre-existing stale anchor target
  `#epss--악용-가능성` → `#epss--악용-확률`.

Stale assets flagged (not removed)
- Walkthrough video on projects.md still captures the pre-post-v2.3
  four-tab layout. Marked in a `<!-- -->` comment; refresh after
  merge.
- Projects-list and global-scans-queue screenshots predate the
  scan-meta row and Project-name column. Marked in `<!-- -->`
  comments; refresh after merge.

Not covered (deferred until on main)
- P2 #4 (Dashboard KPI deep links), P2 #3/#1/#2 (visible in feature
  branches but not yet merged to main).

Verification
- `npm run build` under docs-site exits 0 with no warnings on either
  locale.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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