Organizing Cases: Projects & Sections

Once you have more than a handful of YAMLs on the agent, the flat Test cases list becomes hard to navigate. Four orthogonal axes keep it tidy:

  • Project — a first-class container (TestRail-like), stored in a registry. One case belongs to at most one project. Used by the Project filter.

  • Section — free-text metadata copied from the TestRail section (e.g. TR-181, TR-181 Mesh). The list sorts on it.

  • TestRail run membership — which runs (e.g. Run 319) a case belongs to, held in its own field and filled from TestRail, not inferred.

  • Required ISP mode — whether the case needs Multi-Domain (ISP 1) or Multi-ISP (ISP 2), so it is only run where it can pass.

All four are pure organisation — they never change how a case converts or runs.

Run membership is not the project label

A case’s project records the import path; its testrail_runs field records membership. They diverge: cases pulled in through a section carry a section-shaped project label while still belonging to a run, and cases with near-identical titles can sit in different runs.

When you need "everything in run 319", filter by the TestRail run axis or backfill it (Sync runs… in the Library menu of Test cases, POST /api/testrail/backfill-runs). Reading membership off the project label has silently lost dozens of cases before.

The backfill rewrites case metadata non-atomically — never run it while a batch is executing.

At a glance
  • Projects live in a registry (GET/POST /api/projects). Cases reference a project by name.

  • Project filter dropdown narrows the list to one project (or Unassigned).

  • Section groups the list and is a filter of its own; a case keeps its TestRail section.

  • Bulk assign — tick rows → assign to a project in one call.

  • Backfill org fields (project / refs / priority / type) from TestRail without re-running the converter — POST /api/testrail/backfill-fields.

Projects Registry

A project is a row in the projects SQLite table (scripts/case_db.py), sharing the same DB file as the auth layer. Columns: name (primary key), description, testrail_project_id, created_by, created_at.

Endpoint Role Purpose

GET /api/projects

any authed

List projects, each with a live case_count (YAMLs whose project matches).

POST /api/projects

admin / editor

Create a project (name required; duplicate name → 400).

PATCH /api/projects/{name}

admin / editor

Rename and/or set description. A rename rewrites the project field of every affected YAML.

DELETE /api/projects/{name}

admin / editor

Delete the project (see Unassigned below).

POST /api/tests/assign-project

admin / editor

Bulk-assign a list of files to one project.

The "Unassigned" bucket

There is no literal Unassigned row in the registry. A case whose project is empty ("") is unassigned, and the UI surfaces those under a virtual Unassigned option in the Project filter.

Deleting a project does not delete its cases. The DELETE /api/projects/{name} handler first walks every YAML, and for each case that belonged to the deleted project it clears the project field (orphans it to Unassigned), then removes the registry row. No test is ever lost by a project delete.
Importing from TestRail auto-registers the project (insert-if-absent via ensure_project, created_by="testrail-import"), so you rarely create one by hand. New project in the Library menu of the Test cases header (POST /api/projects) is for projects you want to exist before any case lands in them.

Section Grouping & the Project Filter

The Test cases list is sorted by section first, then by case ID by default, so cases sharing a section sit together. The Sort select in the filter row offers the other orders — Title, Type, Priority, Reference, Last run — and the section itself is one of the filters. The section value comes straight from TestRail (test_case.section) and is editable in the Editor; Set section… in the selection bar writes one section onto every ticked case at once.

In the filter row a Project dropdown narrows what you see:

  • All projects — no project filter (default).

  • Unassigned — only cases with an empty project.

  • <name> (N) — only that project’s cases; N is the live count.

The filter is independent of the Portal filter and the search box — they compose (project AND portal AND text match). The same dropdown appears in the Editor sidebar.

Case Metadata Fields

Beyond section, each case carries a small set of organisational fields. project and section live at the top level of test_case; the rest live under test_case.metadata. They surface in the list as pills/links and in the Editor as form inputs.

Field Stored as Rendering

Project

test_case.project

Drives the Project filter and case_count. Editor exposes a dropdown of registered projects.

Section

test_case.section

Free text; groups the list.

Reference

metadata.references

A Jira issue key (e.g. DEV-1234). Rendered as a clickable link when the jira_base_url setting is set (<jira_base_url><key>), otherwise as plain text. Multiple comma/space-separated refs each become their own link.

Priority

metadata.priority

A coloured pill — Critical (red), High (amber), Medium (blue), Low (green).

Type

metadata.type

A grey pill (e.g. Functional, Regression, Smoke & Sanity).

Author

metadata.created_by / metadata.edited_by

Who imported vs. who last saved an edit. Set automatically; not editable by hand.

TestRail runs

test_case.testrail_runs

Which TestRail runs the case belongs to. Drives the TestRail run filter (behind More filters). Filled on a run import and by Sync runs…; kept deliberately separate from Project. The case list exposes it as runs and GET /api/tests/{file} as testrail_runs — the YAML key is the one spelled here, and a hand-written scalar or string is read as a one-element list.

Required ISP mode

test_case.required_isp

1 (Multi-Domain) or 2 (Multi-ISP), or absent. Drives the ISP mode filter, and lets an env pinned to one mode refuse — rather than silently fail — a case needing the other.

Claims

computed

{total, measured} — how many of the case’s assertions are decided by a deterministic check. Rendered as the claims chip. Not editable: it is derived from the steps.

Blocked

test_case.blocked / blocked_categories

Present only on blocked cases. Rendered as a not measurable / no control / infra pill.

The jira_base_url is read from Settings and exposed (read-only, non-secret) at GET /api/ui-config. Leave it blank and references render as plain text instead of links.

Populating the Fields

There are four ways org fields get filled, from most to least automatic.

1. TestRail import (automatic)

Importing a case resolves and registers its Project from the case’s TestRail project/suite, and maps Priority / Type / Reference from the TestRail priority_id / type_id / refs into metadata. Bare-number refs are key-ified by the converter using a Jira prefix (e.g. 11278 → DEV-11278).

2. Manual edit (Editor)

The Editor form has Project (dropdown), Section, Priority, Type, and Reference inputs. Save persists them into the YAML and stamps edited_by.

3. Bulk assign (multi-select)

Tick several rows and choose Add to project… — in the bulk bar of Test cases, or in the Editor sidebar’s bulk actions — to assign them all in one shot:

curl -X POST http://ai.friendly-tech.com:8083/api/tests/assign-project \
  -H 'Content-Type: application/json' \
  -d '{"files":["C7677.yaml","C7681.yaml"],"project":"TR-181 Mesh"}'

Path components in files are rejected (basename-only, no traversal). The response reports {"assigned": N}.

4. Find / replace (multi-select)

Find / replace… in the same bulk bar rewrites one of title, section, project, priority, type or reference across every ticked case, previewed before it is written — see test-management.adoc#bulk-update.

Which of these writes a version

Find / replace… and Set section… go through the case version recorder, so each edit lands in the case’s change history with a field: before → after line.

Add to project…, a project rename or delete (both rewrite test_case.project on the affected YAMLs), Sync runs… and the field backfill below write the YAML in place, without a version. Nothing is lost — the YAML is the record — but do not expect these four to show up in the Editor’s Change history.

Backfilling Org Fields (no re-convert)

After you have already imported a corpus, you often want to enrich the existing YAMLs with Project / Reference / Priority / Type pulled fresh from TestRail — without re-running Stage 2 LLM enrichment (which would re-cost and risk jitter) and without bumping the converter cache.

POST /api/testrail/backfill-fields does exactly that. It matches local YAMLs to TestRail cases by numeric ID, computes the org-field diff, and patches only those fields.

Field Meaning

project_id / suite_id

Required — the TestRail project + suite to read.

section_id

Optional — narrow to one section.

dry_run

Defaults to true — returns the would-be changes without writing. Set false to apply.

ref_prefix

Prefixes bare-number refs with a Jira key (e.g. DEV-) so 11278 becomes DEV-11278. Tokens that are already keys (DEV-5) are left untouched.

# Preview (no write) — see matched / changed counts + per-case diffs
curl -X POST http://ai.friendly-tech.com:8083/api/testrail/backfill-fields \
  -H 'Content-Type: application/json' \
  -d '{"project_id":1,"suite_id":3,"ref_prefix":"DEV-"}'

# Apply
curl -X POST http://ai.friendly-tech.com:8083/api/testrail/backfill-fields \
  -H 'Content-Type: application/json' \
  -d '{"project_id":1,"suite_id":3,"ref_prefix":"DEV-","dry_run":false}'

The response carries matched (local YAMLs found in the suite), changed (cases whose org fields actually differ), and a details list of per-file diffs. Always run a dry_run first and read the diff before applying.

Backfill is the right tool after a TestRail re-org (a case moved project, got a priority, gained a Jira ref). Use reimport only when the converted output (tree / parameters / steps) needs to change.

See Also

  • Importing Tests — auto project/priority/type/ref assignment happens during import.

  • Editor Guide → Core Fields — editing these fields by hand.

  • Test Management — renaming these fields across many cases at once, and the trash, history and attachments a case gains alongside them.

  • API Reference — the full /api/projects and /api/testrail/backfill-fields surface.