generated from coulomb/repo-seed
feat(classification-spine): implement STATE-WP-0065 repo-anchored model
Replace the ad-hoc coordination-domain spine with the Repo Classification Standard: 14 market domains, classification columns on managed_repos, and workplans anchored by repo_id (topic_id optional). - Add Alembic migration d8e9f0a1b2c3 with data backfill and workstream→workplan rename - Add api/classification.py validation and register-from-classification tooling - Expose workplan-first REST/MCP surface with legacy workstream aliases - Add C-24 consistency rule and legacy domain frontmatter mapping - Update dashboard repos page with category/capability/stake filters - Update orientation docs; mark STATE-WP-0065 finished
This commit is contained in:
@@ -102,3 +102,62 @@ async def client(test_engine):
|
||||
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
|
||||
yield ac
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared entity helpers (workplan-first; legacy workstream names retained)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def create_test_domain(client, slug="infotech", name="Infotech"):
|
||||
r = await client.post("/domains/", json={"slug": slug, "name": name})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
async def create_test_topic(client, domain_slug="infotech", slug="testtopic", title="Test Topic"):
|
||||
r = await client.post("/topics/", json={
|
||||
"slug": slug, "title": title, "domain": domain_slug,
|
||||
})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
async def create_test_repo(client, domain_slug="infotech", slug="test-repo", **extra):
|
||||
payload = {
|
||||
"domain_slug": domain_slug,
|
||||
"slug": slug,
|
||||
"name": "Test Repo",
|
||||
**extra,
|
||||
}
|
||||
r = await client.post("/repos/", json=payload)
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
async def create_test_workplan(
|
||||
client,
|
||||
repo_id,
|
||||
topic_id=None,
|
||||
slug="test-wp",
|
||||
title="Test Workplan",
|
||||
status="active",
|
||||
**extra,
|
||||
):
|
||||
payload = {"repo_id": repo_id, "slug": slug, "title": title, "status": status, **extra}
|
||||
if topic_id is not None:
|
||||
payload["topic_id"] = topic_id
|
||||
r = await client.post("/workplans/", json=payload)
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
async def create_test_workstream(client, topic_id=None, repo_id=None, slug="test-wp", **kwargs):
|
||||
"""Legacy helper name — creates a repo-anchored workplan."""
|
||||
if repo_id is None:
|
||||
domain = await create_test_domain(client)
|
||||
if topic_id is None:
|
||||
topic = await create_test_topic(client, domain_slug=domain["slug"])
|
||||
topic_id = topic["id"]
|
||||
repo = await create_test_repo(client, domain_slug=domain["slug"], slug=f"{slug}-repo")
|
||||
repo_id = repo["id"]
|
||||
return await create_test_workplan(client, repo_id=repo_id, topic_id=topic_id, slug=slug, **kwargs)
|
||||
|
||||
@@ -27,17 +27,19 @@ async def _create_topic(client, domain_slug="testdomain"):
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id):
|
||||
r = await client.post("/workstreams/", json={
|
||||
"topic_id": topic_id, "slug": "test-ws", "title": "Test WS",
|
||||
})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
|
||||
|
||||
async def _create_task(client, workstream_id, title="Test task", status="wait"):
|
||||
async def _create_workstream(client, topic_id, domain_slug="custodian"):
|
||||
repo = await create_test_repo(client, domain_slug=domain_slug, slug="test-repo")
|
||||
return await create_test_workplan(
|
||||
client, repo_id=repo["id"], topic_id=topic_id, slug="test-ws", title="Test WS",
|
||||
)
|
||||
|
||||
|
||||
async def _create_task(client, workplan_id, title="Test task", status="wait"):
|
||||
r = await client.post("/tasks/", json={
|
||||
"workstream_id": workstream_id, "title": title,
|
||||
"workplan_id": workplan_id, "title": title,
|
||||
})
|
||||
assert r.status_code == 201, r.text
|
||||
task = r.json()
|
||||
|
||||
67
tests/test_classification.py
Normal file
67
tests/test_classification.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Tests for api.classification validation module (STATE-WP-0065 P2)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from api.classification import (
|
||||
ClassificationData,
|
||||
validate_classification,
|
||||
)
|
||||
|
||||
|
||||
def _valid_block(**overrides) -> dict:
|
||||
base = {
|
||||
"category": "tooling",
|
||||
"domain": "infotech",
|
||||
"secondary_domains": [],
|
||||
"capability_tags": ["platform"],
|
||||
"business_stake": ["technology", "operations"],
|
||||
"business_mechanics": ["coordination"],
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
class TestValidateClassification:
|
||||
def test_valid_block_passes(self):
|
||||
errors, warnings = validate_classification(_valid_block())
|
||||
assert errors == []
|
||||
|
||||
def test_missing_category_fails(self):
|
||||
block = _valid_block()
|
||||
del block["category"]
|
||||
errors, _ = validate_classification(block)
|
||||
assert any("category" in err for err in errors)
|
||||
|
||||
def test_invalid_category_fails(self):
|
||||
errors, _ = validate_classification(_valid_block(category="not-a-category"))
|
||||
assert any("category" in err for err in errors)
|
||||
|
||||
def test_invalid_domain_fails(self):
|
||||
errors, _ = validate_classification(_valid_block(domain="not-a-domain"))
|
||||
assert any("domain" in err for err in errors)
|
||||
|
||||
def test_unknown_capability_tag_warns(self):
|
||||
_, warnings = validate_classification(_valid_block(capability_tags=["totally-made-up-tag"]))
|
||||
assert any("capability_tag" in warn for warn in warnings)
|
||||
|
||||
def test_invalid_business_stake_fails(self):
|
||||
errors, _ = validate_classification(_valid_block(business_stake=["not-a-stake"]))
|
||||
assert any("business_stake" in err for err in errors)
|
||||
|
||||
def test_secondary_domain_repeats_primary_fails(self):
|
||||
errors, _ = validate_classification(
|
||||
_valid_block(domain="infotech", secondary_domains=["infotech"])
|
||||
)
|
||||
assert any("repeats the primary domain" in err for err in errors)
|
||||
|
||||
|
||||
class TestClassificationData:
|
||||
def test_round_trip_dict(self):
|
||||
block = _valid_block(classified_at="2026-06-22", classified_by="human", version="1.0")
|
||||
data = ClassificationData.from_block(block)
|
||||
payload = data.to_dict()
|
||||
assert payload["category"] == "tooling"
|
||||
assert payload["domain"] == "infotech"
|
||||
assert payload["classified_by"] == "human"
|
||||
assert payload["standard_version"] == "1.0"
|
||||
@@ -23,6 +23,7 @@ import pytest
|
||||
# Make scripts/ importable without installing
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from api.classification import resolve_topic_domain_slug
|
||||
from consistency_check import (
|
||||
ConsistencyReport,
|
||||
Issue,
|
||||
@@ -54,6 +55,15 @@ from api.workplan_status import ready_review_status
|
||||
# for backward compat; their canonical implementations live in repo_sync.py.
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# domain mapping (STATE-WP-0065 P4)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestResolveTopicDomainSlug:
|
||||
def test_workplan_custodian_maps_to_infotech(self):
|
||||
assert resolve_topic_domain_slug("custodian", repo_market_domain="infotech") == "infotech"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# parse_frontmatter
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -372,7 +382,7 @@ class TestRenderText:
|
||||
r.add(severity="WARN", check_id="C-04", message="w")
|
||||
r.add(severity="INFO", check_id="C-08", message="i")
|
||||
text = render_text(r)
|
||||
assert "1 fail" in text
|
||||
assert "1 assessment-fail" in text
|
||||
assert "1 warn" in text
|
||||
assert "1 info" in text
|
||||
|
||||
@@ -443,7 +453,7 @@ class TestReportToDict:
|
||||
r = ConsistencyReport(repo_slug="r", repo_path="/p")
|
||||
d = report_to_dict(r)
|
||||
assert d["result"] == "pass"
|
||||
assert d["summary"] == {"fail": 0, "warn": 0, "info": 0}
|
||||
assert d["summary"] == {"fail": 0, "automation_error": 0, "warn": 0, "info": 0}
|
||||
assert d["issues"] == []
|
||||
|
||||
def test_fail_result(self):
|
||||
|
||||
@@ -17,8 +17,20 @@ async def _create_topic(client, domain_slug="legacy-domain", slug="legacy-topic"
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workplan(client, topic_id, slug="legacy-wp", title="Legacy WP"):
|
||||
async def _create_repo(client, domain_slug="legacy-domain", slug="legacy-repo"):
|
||||
r = await client.post("/repos/", json={
|
||||
"domain_slug": domain_slug,
|
||||
"slug": slug,
|
||||
"name": "Legacy Repo",
|
||||
})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workplan(client, topic_id, domain_slug="legacy-domain", slug="legacy-wp", title="Legacy WP"):
|
||||
repo = await _create_repo(client, domain_slug=domain_slug, slug=f"{slug}-repo")
|
||||
r = await client.post("/workplans/", json={
|
||||
"repo_id": repo["id"],
|
||||
"topic_id": topic_id,
|
||||
"slug": slug,
|
||||
"title": title,
|
||||
|
||||
@@ -28,12 +28,14 @@ async def _create_topic(client, domain_slug="mcp-domain"):
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id):
|
||||
r = await client.post("/workstreams/", json={
|
||||
"topic_id": topic_id, "slug": "mcp-ws", "title": "MCP Workstream",
|
||||
})
|
||||
assert r.status_code == 201
|
||||
return r.json()
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id, domain_slug="mcp-domain"):
|
||||
repo = await create_test_repo(client, domain_slug=domain_slug, slug="mcp-repo")
|
||||
return await create_test_workplan(
|
||||
client, repo_id=repo["id"], topic_id=topic_id, slug="mcp-ws", title="MCP Workstream",
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -19,15 +19,16 @@ async def _call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
class TestMCPWriteTools:
|
||||
async def test_create_workstream_returns_rest_shape_and_emits_progress(self, monkeypatch):
|
||||
async def test_create_workplan_returns_rest_shape_and_emits_progress(self, monkeypatch):
|
||||
calls: list[tuple[str, dict[str, Any]]] = []
|
||||
|
||||
def fake_post(path: str, body: dict[str, Any]) -> dict[str, Any]:
|
||||
calls.append((path, body))
|
||||
if path == "/workstreams":
|
||||
if path == "/workplans":
|
||||
return {
|
||||
"id": "ws-1",
|
||||
"topic_id": body["topic_id"],
|
||||
"id": "wp-1",
|
||||
"repo_id": body["repo_id"],
|
||||
"topic_id": body.get("topic_id"),
|
||||
"title": body["title"],
|
||||
"slug": body["slug"],
|
||||
"status": body["status"],
|
||||
@@ -39,20 +40,42 @@ class TestMCPWriteTools:
|
||||
monkeypatch.setattr(server, "_post", fake_post)
|
||||
|
||||
body = await _call_tool(
|
||||
"create_workstream",
|
||||
{"topic_id": "topic-1", "title": "MCP Reliable Write"},
|
||||
"create_workplan",
|
||||
{"repo_id": "repo-1", "topic_id": "topic-1", "title": "MCP Reliable Write"},
|
||||
)
|
||||
|
||||
assert body == {
|
||||
"id": "ws-1",
|
||||
"id": "wp-1",
|
||||
"repo_id": "repo-1",
|
||||
"topic_id": "topic-1",
|
||||
"title": "MCP Reliable Write",
|
||||
"slug": "mcp-reliable-write",
|
||||
"status": "active",
|
||||
}
|
||||
assert [path for path, _ in calls] == ["/workstreams", "/progress"]
|
||||
assert calls[1][1]["workstream_id"] == "ws-1"
|
||||
assert calls[1][1]["event_type"] == "workstream_created"
|
||||
assert [path for path, _ in calls] == ["/workplans", "/progress"]
|
||||
assert calls[1][1]["workplan_id"] == "wp-1"
|
||||
assert calls[1][1]["event_type"] == "workplan_created"
|
||||
|
||||
async def test_create_workstream_legacy_alias_uses_workplans_endpoint(self, monkeypatch):
|
||||
calls: list[tuple[str, dict[str, Any]]] = []
|
||||
|
||||
def fake_post(path: str, body: dict[str, Any]) -> dict[str, Any]:
|
||||
calls.append((path, body))
|
||||
if path == "/workplans":
|
||||
return {"id": "wp-1", "repo_id": body["repo_id"], "title": body["title"], "slug": body["slug"], "status": "active"}
|
||||
if path == "/progress":
|
||||
return {"id": "event-1", **body}
|
||||
raise AssertionError(f"unexpected POST {path}")
|
||||
|
||||
monkeypatch.setattr(server, "_post", fake_post)
|
||||
|
||||
body = await _call_tool(
|
||||
"create_workstream",
|
||||
{"repo_id": "repo-1", "title": "Legacy alias"},
|
||||
)
|
||||
|
||||
assert body["id"] == "wp-1"
|
||||
assert [path for path, _ in calls] == ["/workplans", "/progress"]
|
||||
|
||||
async def test_create_task_returns_rest_shape_and_emits_progress(self, monkeypatch):
|
||||
calls: list[tuple[str, dict[str, Any]]] = []
|
||||
@@ -62,7 +85,8 @@ class TestMCPWriteTools:
|
||||
if path == "/tasks":
|
||||
return {
|
||||
"id": "task-1",
|
||||
"workstream_id": body["workstream_id"],
|
||||
"workplan_id": body.get("workplan_id") or body.get("workstream_id"),
|
||||
"workstream_id": body.get("workplan_id") or body.get("workstream_id"),
|
||||
"title": body["title"],
|
||||
"priority": body["priority"],
|
||||
"status": "todo",
|
||||
@@ -80,6 +104,7 @@ class TestMCPWriteTools:
|
||||
|
||||
assert body == {
|
||||
"id": "task-1",
|
||||
"workplan_id": "ws-1",
|
||||
"workstream_id": "ws-1",
|
||||
"title": "MCP task",
|
||||
"priority": "high",
|
||||
@@ -266,18 +291,18 @@ class TestMCPWriteTools:
|
||||
|
||||
def fake_post(path: str, body: dict[str, Any]) -> dict[str, Any]:
|
||||
calls.append((path, body))
|
||||
return {"error": "API 422: invalid topic"}
|
||||
return {"error": "API 422: invalid repo"}
|
||||
|
||||
monkeypatch.setattr(server, "_post", fake_post)
|
||||
|
||||
body = await _call_tool(
|
||||
"create_workstream",
|
||||
{"topic_id": "bad-topic", "title": "No progress on failure"},
|
||||
{"repo_id": "bad-repo", "title": "No progress on failure"},
|
||||
)
|
||||
|
||||
assert body["tool"] == "create_workstream"
|
||||
assert body["error"] == "API 422: invalid topic"
|
||||
assert [path for path, _ in calls] == ["/workstreams"]
|
||||
assert body["error"] == "API 422: invalid repo"
|
||||
assert [path for path, _ in calls] == ["/workplans"]
|
||||
|
||||
async def test_record_decision_missing_id_is_clear_and_skips_progress(self, monkeypatch):
|
||||
calls: list[tuple[str, dict[str, Any]]] = []
|
||||
|
||||
@@ -20,14 +20,18 @@ async def _create_topic(client, domain_slug="digest", slug="digest-topic", title
|
||||
return response.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id, slug="digest-ws", title="Digest Workstream"):
|
||||
response = await client.post("/workstreams/", json={"topic_id": topic_id, "slug": slug, "title": title})
|
||||
assert response.status_code == 201, response.text
|
||||
return response.json()
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
|
||||
|
||||
async def _create_task(client, workstream_id, title="Digest task"):
|
||||
response = await client.post("/tasks/", json={"workstream_id": workstream_id, "title": title})
|
||||
async def _create_workstream(client, topic_id, slug="digest-ws", title="Digest Workstream", domain_slug="digest"):
|
||||
repo = await create_test_repo(client, domain_slug=domain_slug, slug=f"{slug}-repo")
|
||||
return await create_test_workplan(
|
||||
client, repo_id=repo["id"], topic_id=topic_id, slug=slug, title=title,
|
||||
)
|
||||
|
||||
|
||||
async def _create_task(client, workplan_id, title="Digest task"):
|
||||
response = await client.post("/tasks/", json={"workplan_id": workplan_id, "title": title})
|
||||
assert response.status_code == 201, response.text
|
||||
return response.json()
|
||||
|
||||
@@ -277,12 +281,16 @@ class TestRecentlyOnScopeRoutes:
|
||||
|
||||
await _create_domain(client, slug="broken", name="Broken Domain")
|
||||
broken_topic = await _create_topic(client, domain_slug="broken", slug="broken-topic")
|
||||
broken_workstream = await _create_workstream(client, broken_topic["id"], slug="broken-ws")
|
||||
broken_workstream = await _create_workstream(
|
||||
client, broken_topic["id"], slug="broken-ws", domain_slug="broken",
|
||||
)
|
||||
await _create_task(client, broken_workstream["id"], title="Broken source")
|
||||
|
||||
await _create_domain(client, slug="good", name="Good Domain")
|
||||
good_topic = await _create_topic(client, domain_slug="good", slug="good-topic")
|
||||
good_workstream = await _create_workstream(client, good_topic["id"], slug="good-ws")
|
||||
good_workstream = await _create_workstream(
|
||||
client, good_topic["id"], slug="good-ws", domain_slug="good",
|
||||
)
|
||||
await _create_task(client, good_workstream["id"], title="Good source")
|
||||
|
||||
response = await client.post("/recently-on-scope/hourly", json={"range": "1d"})
|
||||
|
||||
71
tests/test_register_from_classification.py
Normal file
71
tests/test_register_from_classification.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Tests for register_from_classification CLI (STATE-WP-0065 P3)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SCRIPT = REPO_ROOT / "scripts" / "register_from_classification.py"
|
||||
|
||||
|
||||
def test_cli_help():
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(SCRIPT), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=REPO_ROOT,
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert "--repo-path" in result.stdout
|
||||
assert "--bulk" in result.stdout
|
||||
assert "--dry-run" in result.stdout
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dry_run_repo_path_state_hub():
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
from scripts.register_from_classification import run_registration
|
||||
import argparse
|
||||
|
||||
args = argparse.Namespace(
|
||||
repo_path=str(REPO_ROOT),
|
||||
slug=None,
|
||||
bulk=False,
|
||||
dry_run=True,
|
||||
api=False,
|
||||
db=False,
|
||||
api_base="http://127.0.0.1:8000",
|
||||
json=False,
|
||||
)
|
||||
report = await run_registration(args)
|
||||
counts = report.counts()
|
||||
assert counts["invalid"] == 0
|
||||
assert counts["registered"] + counts["updated"] + counts["skipped"] >= 1
|
||||
assert any(r.slug == "state-hub" for r in report.results)
|
||||
# Valid classification file is always parsed even when DB domains are absent.
|
||||
assert not any("repo_classification block" in r.detail for r in report.results)
|
||||
|
||||
|
||||
def test_json_report_shape():
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(SCRIPT),
|
||||
"--repo-path",
|
||||
str(REPO_ROOT),
|
||||
"--dry-run",
|
||||
"--json",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=REPO_ROOT,
|
||||
)
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["summary"]["invalid"] == 0
|
||||
assert "summary" in payload
|
||||
assert "results" in payload
|
||||
assert set(payload["summary"]) == {"registered", "updated", "skipped", "invalid"}
|
||||
@@ -28,19 +28,34 @@ async def _create_topic(client, domain_slug="testdomain", slug="testtopic", titl
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id, slug="test-ws", title="Test WS", status="active", **extra):
|
||||
async def _create_workplan(client, repo_id, topic_id=None, slug="test-ws", title="Test WS", status="active", **extra):
|
||||
payload = {
|
||||
"topic_id": topic_id, "slug": slug, "title": title, "status": status,
|
||||
"repo_id": repo_id, "slug": slug, "title": title, "status": status,
|
||||
}
|
||||
if topic_id is not None:
|
||||
payload["topic_id"] = topic_id
|
||||
payload.update(extra)
|
||||
r = await client.post("/workstreams/", json=payload)
|
||||
r = await client.post("/workplans/", json=payload)
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_task(client, workstream_id, title="Test task"):
|
||||
async def _create_workstream(client, topic_id=None, repo_id=None, slug="test-ws", title="Test WS", status="active", **extra):
|
||||
if repo_id is None:
|
||||
if topic_id is None:
|
||||
await _create_domain(client)
|
||||
topic = await _create_topic(client)
|
||||
topic_id = topic["id"]
|
||||
repo = await _create_repo(client, slug=f"{slug}-repo")
|
||||
repo_id = repo["id"]
|
||||
return await _create_workplan(
|
||||
client, repo_id=repo_id, topic_id=topic_id, slug=slug, title=title, status=status, **extra
|
||||
)
|
||||
|
||||
|
||||
async def _create_task(client, workplan_id, title="Test task"):
|
||||
r = await client.post("/tasks/", json={
|
||||
"workstream_id": workstream_id, "title": title,
|
||||
"workplan_id": workplan_id, "title": title,
|
||||
})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
@@ -16,19 +16,20 @@ async def _create_topic(client, domain_slug: str = "bulk-domain"):
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id: str):
|
||||
r = await client.post(
|
||||
"/workstreams/",
|
||||
json={"topic_id": topic_id, "slug": "bulk-ws", "title": "Bulk Workstream"},
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id: str, domain_slug: str = "bulk-domain"):
|
||||
repo = await create_test_repo(client, domain_slug=domain_slug, slug="bulk-repo")
|
||||
return await create_test_workplan(
|
||||
client, repo_id=repo["id"], topic_id=topic_id, slug="bulk-ws", title="Bulk Workstream",
|
||||
)
|
||||
assert r.status_code == 201
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_task(client, workstream_id: str, title: str):
|
||||
async def _create_task(client, workplan_id: str, title: str):
|
||||
r = await client.post(
|
||||
"/tasks/",
|
||||
json={"workstream_id": workstream_id, "title": title},
|
||||
json={"workplan_id": workplan_id, "title": title},
|
||||
)
|
||||
assert r.status_code == 201
|
||||
return r.json()
|
||||
|
||||
@@ -25,14 +25,16 @@ async def _create_topic(client, domain_slug="testdomain"):
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id, slug="ws1"):
|
||||
r = await client.post("/workstreams/", json={"topic_id": topic_id, "slug": slug, "title": "WS"})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
|
||||
|
||||
async def _create_task(client, workstream_id):
|
||||
r = await client.post("/tasks/", json={"workstream_id": workstream_id, "title": "task"})
|
||||
async def _create_workstream(client, topic_id, slug="ws1", domain_slug="testdomain"):
|
||||
repo = await create_test_repo(client, domain_slug=domain_slug, slug=f"{slug}-repo")
|
||||
return await create_test_workplan(client, repo_id=repo["id"], topic_id=topic_id, slug=slug, title="WS")
|
||||
|
||||
|
||||
async def _create_task(client, workplan_id):
|
||||
r = await client.post("/tasks/", json={"workplan_id": workplan_id, "title": "task"})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
@@ -25,14 +25,16 @@ async def _create_topic(client, domain_slug="td"):
|
||||
return r.json()
|
||||
|
||||
|
||||
async def _create_workstream(client, topic_id):
|
||||
r = await client.post("/workstreams/", json={"topic_id": topic_id, "slug": "ws", "title": "WS"})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
from tests.conftest import create_test_repo, create_test_workplan
|
||||
|
||||
|
||||
async def _create_task(client, workstream_id, title="my task"):
|
||||
r = await client.post("/tasks/", json={"workstream_id": workstream_id, "title": title})
|
||||
async def _create_workstream(client, topic_id, domain_slug="td"):
|
||||
repo = await create_test_repo(client, domain_slug=domain_slug, slug="td-repo")
|
||||
return await create_test_workplan(client, repo_id=repo["id"], topic_id=topic_id, slug="ws", title="WS")
|
||||
|
||||
|
||||
async def _create_task(client, workplan_id, title="my task"):
|
||||
r = await client.post("/tasks/", json={"workplan_id": workplan_id, "title": title})
|
||||
assert r.status_code == 201, r.text
|
||||
return r.json()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user