Skip to content

PHP 8 fixes, performance indexes, REST v3 expansion, modern SPA UI, webhooks/OIDC, MCP server#413

Open
hupark-seegene wants to merge 10 commits into
TestLinkOpenSourceTRMS:testlink_1_9_20_fixedfrom
hupark-seegene:php8-modernization
Open

PHP 8 fixes, performance indexes, REST v3 expansion, modern SPA UI, webhooks/OIDC, MCP server#413
hupark-seegene wants to merge 10 commits into
TestLinkOpenSourceTRMS:testlink_1_9_20_fixedfrom
hupark-seegene:php8-modernization

Conversation

@hupark-seegene

Copy link
Copy Markdown

Summary

This PR modernizes TestLink 1.9.20 while keeping the legacy application fully intact — everything is additive. All changes were runtime-verified against a live instance seeded with 10,000 test cases / 500,000 executions. A detailed write-up with before/after code, measured numbers, install steps and cautions lives in docs/modernization/MODERNIZATION.md in this branch.

What's inside (10 commits, reviewable independently)

  1. fix: PHP 8 fatal errors — undefined LINKS_* constants + removed split() in string_api.php (crashed any URL-containing value), (arrya) parse error that left the whole XML-RPC v1 API dead, bare constant in the REST v3 custom example, ${var} interpolation, optional-before-required params, REST auth now uses case-insensitive PSR-7 getHeaderLine() and actually returns its 401.
  2. perf: execution indexes — new 1.9.21 migration; plan-scoped dashboard queries no longer full-scan (status totals 123ms → 46ms @500k rows; flaky analysis no longer exceeds memory_limit).
  3. feat: webhooks + generic OIDC + trend/flaky report — HMAC-signed execution webhooks; OIDC provider for any spec-compliant IdP with .well-known discovery; dependency-free SVG trend report for the classic UI.
  4. feat: REST v3 expansion + SPA — ~60 JSON endpoints and a React 19 + Vite + Tailwind SPA (ui/, ~103KB gz, served statically) covering dashboard, case authoring, requirements coverage, execution with evidence attachments and assignments, result matrix, plans/builds/milestones, reports with CSV, admin, global search. Localized en/ko/vi (typed dictionaries — an incomplete translation is a compile error).
  5. feat: printable documents + TestLink-XML import/export — reuses the legacy exporter classes so the XML schema stays byte-compatible; import round-trip verified.
  6. feat: MCP servermcp/ exposes 19 tools over the REST API for AI-assisted test management (Model Context Protocol, stdio), plus a ready-made Claude Code agent definition.

Compatibility

  • Legacy frames UI untouched and fully functional; the SPA is opt-in.
  • No schema changes besides two new indexes (migration file included).
  • Existing REST/XML-RPC callers keep their behavior (one intentional change: a missing Apikey now correctly returns 401 instead of 200).

Verification

php -l clean across first-party code; every endpoint curl-verified; SPA flows browser-verified; XML export/import round-trip compared equal; perf numbers measured on 500k executions.

Happy to split this into smaller PRs (the commits are already sliced that way) if that makes review easier.

🤖 Generated with Claude Code (Claude Fable 5)

HyeonungPark and others added 10 commits July 22, 2026 09:48
- string_api.php: define LINKS_SAME_WINDOW / LINKS_NEW_WINDOW
  (adapted from MantisBT, referenced but never defined — fatal on
  PHP 8 when rendering any value containing a URL), replace removed
  split() with explode(), pass -1 instead of null to preg_split()
- xmlrpc.class.php: fix (arrya) cast typo that was a parse error,
  leaving the whole XML-RPC v1 API dead
- RestApiCustomExample.class.php: quote bare 'lib' constant
  (fatal on load of REST v3 API)
- testplan.class.php: replace ${var} string interpolation
  (deprecated in PHP 8.2)
- tcImport.php / tcCreateFromIssue.php / neverRunByPP.php: fix
  optional-parameter-before-required declarations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
The dashboard/metrics queries filter executions by test plan and
resolve the latest execution per test case version. The historic
executions_idx1 index starts with tcversion_id, so plan-scoped scans
degraded to full table scans (measured over 500k rows).

- idx_exec_tplan_tcv_id (testplan_id, tcversion_id, id) backs
  latest-per-version lookups and flip/flaky analysis
- idx_exec_tplan_ts (testplan_id, execution_ts) backs daily trends

Measured on 500k executions: status totals 123ms -> 46ms when
combined with a MAX(id) loose-index-scan query shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
Webhooks: new lib/functions/webhook_api.php sends HMAC-SHA256-signed
JSON POSTs (X-TestLink-Signature, GitHub-style) to URLs configured in
$tlCfg->webhooks when executions are recorded (UI and API write
paths). Best-effort with short timeouts so a dead receiver cannot
block the user-facing operation. Disabled by default.

OIDC: new generic OpenID Connect provider (Keycloak, Okta, Auth0,
Azure AD v2, any spec-compliant IdP). Endpoints configurable
explicitly or resolved from .well-known/openid-configuration.
Identity is read from the userinfo endpoint over TLS, so no local
JWT verification is required. Sample config in cfg/oauth_samples/.
Also fixes an undefined $oauthCfg variable in the azuread branch of
OAuth2Call.php.

Reports: new "Execution Trend & Flaky Tests" report — daily
executions by status as a dependency-free server-side SVG chart,
plus a ranking of test case versions whose executions flip between
pass and fail. Registered in the reports menu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
REST v3 additions backing the new UI (and useful standalone):
- POST /auth/login — exchanges login/password for the user's API key
  (auth middleware now uses case-insensitive PSR-7 getHeaderLine()
  and actually returns the 401 it builds)
- pagination for GET /testprojects/{mixedID}/testcases (?page=&limit=)
  and fix for the route arg mismatch that broke it
- suites tree, suite test cases, test case detail (steps + recent
  runs), test case update (steps replace), plan link, plan summary /
  trend / flaky / execution queue / result matrix (by case and by
  suite), builds by plan id
- plan status totals use a MAX(id) loose-index-scan + PK join so the
  cost follows the number of test case versions, not executions;
  flaky analysis is bounded to a configurable recent window

ui/: React 19 + Vite + TanStack Router/Query + Tailwind v4 SPA
covering the daily workflow — dashboard (verdict wall, trend, flaky),
test case browser with suite tree + case editor (steps CRUD, new
suite/case, add-to-plan), execution run queue with verdict recorder,
result matrix (at-a-glance by-suite ratio bars with drill-down to
case level), plans & builds management, reports. Screens deep-link
to each other (?caseId=). Dev: cd ui && npm run dev. Build output
ui/dist is served by the PHP web server as static files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
- REST: GET /users, POST /testplans/{id}/assign (replace-style
  execution assignments on user_assignments, userID 0 unassigns),
  queue gains assignee join and assignedTo filter,
  GET /testplans/{id}/byTester and /byBuild (latest execution per
  version+build, so re-runs don't double count)
- Run screen: assignee filter (everyone/unassigned/user), assignee
  badge in the queue, assign-to select in the verdict recorder
- Reports screen: build comparison and by-tester tables
- ui/ i18n: built-in typed dictionary (no new dependency), locales
  en / ko / vi, 108 keys each — the en dictionary is the source of
  truth and tsc rejects incomplete translations; language switcher
  in the sidebar, choice persisted, default from browser language

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
REST v3:
- GET /testprojects/{id}/search — find cases by external id, name or
  summary text, scoped to the project's (nested) suite subtree
- execution attachments: multipart upload, listing, and authenticated
  binary download (the JSON content-type middleware now respects a
  type set by the handler); case detail carries per-run attachments
- getPlanFlaky streams rows instead of materializing the window —
  large windows used to exceed memory_limit on big installations

ui/:
- global search box in the header with live results and deep links
- evidence upload in the Run verdict recorder; attachment chips with
  authenticated viewing in case detail recent runs
- Plans screen: create project, close/reopen builds
- CSV export for the case matrix and the by-tester report
- deep links now accept both quoted and bare caseId values

MCP:
- mcp/ — TypeScript Model Context Protocol server (stdio) exposing
  19 agent-ergonomic tools over the REST API (search_cases, get_case,
  create_case, record_execution, plan_status, plan_flaky, ...)
- .mcp.json — project-level registration for Claude Code
- .claude/agents/testlink-qa.md — QA operator subagent definition

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
Requirements:
- REST: req spec tree, spec/requirement creation (through the legacy
  requirement managers so revisions and version nodes stay correct),
  requirement detail, test case coverage link/unlink (idempotent),
  and a plan-scoped per-requirement verdict rollup that reuses the
  latest-execution loose-scan pattern
- SPA /requirements: spec tree -> requirement list -> detail with
  covered cases (search-to-cover, unlink, deep links) plus the plan
  coverage panel with verdict ratio bars
- note: requirement_spec_mgr::create()'s default $type references an
  undefined constant (PHP 8 fatal); the endpoint passes the type
  explicitly like every legacy caller does

Admin:
- REST: users (list now carries email/role/active), user creation
  (verified end-to-end: created user can authenticate), activate/
  deactivate, project keywords list/create/delete (cleans keyword
  links like the legacy manager), platforms list/create, linked
  custom fields (read-only)
- SPA /admin: Users / Keywords / Platforms / Custom fields tabs

i18n: 63 new keys per locale (en/ko/vi).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
…yword report

Documents:
- GET /testprojects/{id}/document?type=spec[&suiteID=] — printable,
  self-contained HTML test specification (suite hierarchy, case
  attributes, steps tables); whole-project output is capped with a
  visible truncation note
- GET /testplans/{id}/document?type=report[&buildID=] — printable
  test report: per-suite verdict summary plus per-case latest
  verdicts with tester/date/notes

XML:
- GET /testsuites/{id}/xml reuses the legacy exporter classes, so
  the schema stays byte-compatible with classic TestLink exports
  (two sessionless-execution fixes: cwd-relative require in the
  platform helper, and warning suppression)
- POST /testsuites/{id}/xml imports that same format (cases + steps,
  duplicate names skipped); round-trip verified: export -> import ->
  re-export with identical case and per-case step counts

Milestones & keyword report:
- GET/POST/DELETE milestones with plan progress (executed% / pass%)
  computed via the latest-execution loose scan
- GET /testplans/{id}/byKeyword — per-keyword verdict rollup joined
  through testcase_keywords

SPA: suite toolbar (export XML / spec document / import XML with
result flash), test report button on Reports, Milestones panel on
Plans, By-keyword panel on Reports. New strings in en/ko/vi.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
Full description of the php8-modernization branch: base/after
versions, before/after code for the key PHP 8 fixes and query
reshapes, measured performance table (500k-execution dataset),
feature inventory (webhooks, OIDC, REST v3 endpoints, SPA, MCP),
installation additions, cautions, and before/after screenshots.
The existing README is untouched and remains authoritative for
classic TestLink.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
On a high-latency link the dashboard was serialized into a 3-hop
request waterfall (projects -> plans -> summary/trend/flaky), ~2.1s
end to end even though each origin response is <10ms — the cost was
three sequential round trips.

The workspace now exposes the remembered project/plan ids from
localStorage, so on a revisit the plans lookup and the dashboard
queries fire immediately in parallel with the projects fetch instead
of waiting for it. Measured: dashboard data ready 2135ms -> 787ms.

Also: nginx serves the content-hashed /ui/dist/assets/ with a
1-year immutable cache (index.html stays no-cache), so revisits load
the SPA shell from disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAUVXjSm6HedDGCi8dkEZX
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.

2 participants