|
|
@@ -0,0 +1,151 @@
|
|
|
+"""The understanding bundle (goal 24 task 2): the project Understanding
|
|
|
+ledger — the /api/saddle/understanding insert/read callback pair, the
|
|
|
+immutable history rows, and the current document riding the assignment
|
|
|
+payload."""
|
|
|
+from harness import * # noqa: F401,F403 — the shared wire helpers
|
|
|
+
|
|
|
+
|
|
|
+def run(ctx):
|
|
|
+ auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
|
|
|
+
|
|
|
+ def db_rows(sql, params=()):
|
|
|
+ conn = db()
|
|
|
+ try:
|
|
|
+ return conn.execute(sql, params).fetchall()
|
|
|
+ finally:
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+ print("== understanding: the migration ==")
|
|
|
+ # A fresh database: M0001..M0004 ran at startup, so the table
|
|
|
+ # exists with the goal-24 shape.
|
|
|
+ tables = [r[0] for r in db_rows(
|
|
|
+ "select name from sqlite_master where type = 'table'")]
|
|
|
+ check("project_understandings" in tables,
|
|
|
+ "M0004 creates the project_understandings table on a fresh DB")
|
|
|
+ columns = [r[1] for r in db_rows("pragma table_info(project_understandings)")]
|
|
|
+ check(columns == ["id", "project_id", "body", "commit_hash", "task_id",
|
|
|
+ "created_at"],
|
|
|
+ "the table carries the goal-24 columns (%s)" % columns)
|
|
|
+
|
|
|
+ print("== understanding: POST auth + validation ==")
|
|
|
+ status, _ = saddle_api("POST", "/api/saddle/understanding", None,
|
|
|
+ {"project": "demo", "body": "x"})
|
|
|
+ check(status == 401, "POST rejects a missing token")
|
|
|
+ status, _ = saddle_api("POST", "/api/saddle/understanding", "saddle-bogus",
|
|
|
+ {"project": "demo", "body": "x"})
|
|
|
+ check(status == 401, "POST rejects a wrong token")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "no-such-project", "body": "x"})
|
|
|
+ check(status == 400 and body.get("ok") is False,
|
|
|
+ "POST rejects an unknown project")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "demo", "body": " "})
|
|
|
+ check(status == 400 and body.get("ok") is False,
|
|
|
+ "POST rejects an empty body")
|
|
|
+
|
|
|
+ print("== understanding: inserts append history ==")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "demo",
|
|
|
+ "body": "# Demo understanding\n\nFirst revision.",
|
|
|
+ "commit_hash": "abc1000", "task_id": 11})
|
|
|
+ check(status == 200 and body.get("ok") is True
|
|
|
+ and int(body.get("understanding_id", 0)) > 0,
|
|
|
+ "POST inserts and returns the understanding id")
|
|
|
+ first_id = int(body["understanding_id"])
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "demo",
|
|
|
+ "body": "# Demo understanding\n\nSecond revision.",
|
|
|
+ "commit_hash": "def2000"})
|
|
|
+ check(status == 200 and int(body["understanding_id"]) > first_id,
|
|
|
+ "a refresh appends a LATER history row")
|
|
|
+ second_id = int(body["understanding_id"])
|
|
|
+ rows = db_rows("select id, body, commit_hash, task_id"
|
|
|
+ " from project_understandings order by id")
|
|
|
+ check([r[0] for r in rows] == [first_id, second_id],
|
|
|
+ "history rows are immutable appends in insert order")
|
|
|
+ check(rows[0][2] == "abc1000" and rows[0][3] == 11
|
|
|
+ and rows[1][2] == "def2000" and rows[1][3] == 0,
|
|
|
+ "rows keep the commit hash and the optional task id")
|
|
|
+
|
|
|
+ print("== understanding: GET reads the current revision ==")
|
|
|
+ status, _ = saddle_api("GET", "/api/saddle/understanding?project=demo", None)
|
|
|
+ check(status == 401, "GET rejects a missing token")
|
|
|
+ status, body = saddle_api(
|
|
|
+ "GET", "/api/saddle/understanding?project=no-such-project", fake_token)
|
|
|
+ check(status == 400, "GET rejects an unknown project")
|
|
|
+ status, body = saddle_api("GET", "/api/saddle/understanding", fake_token)
|
|
|
+ check(status == 400, "GET requires the project query parameter")
|
|
|
+ status, body = saddle_api("GET", "/api/saddle/understanding?project=demo",
|
|
|
+ fake_token)
|
|
|
+ check(status == 200 and body.get("ok") is True,
|
|
|
+ "GET answers with the current understanding")
|
|
|
+ current = body.get("understanding") or {}
|
|
|
+ check(current.get("body") == "# Demo understanding\n\nSecond revision.",
|
|
|
+ "GET returns the NEWEST revision (%r)" % current.get("body", "")[:40])
|
|
|
+ check(current.get("commit_hash") == "def2000"
|
|
|
+ and (current.get("created") or "").endswith("Z"),
|
|
|
+ "GET carries the commit hash and an ISO-8601 stamp")
|
|
|
+ # A project without any understanding answers null, not an error.
|
|
|
+ state = slot_public(auth, "/projects", "projects")
|
|
|
+ act(auth, state["create"], {"name": "plain",
|
|
|
+ "git_url": "https://example.com/plain.git"})
|
|
|
+ status, body = saddle_api("GET", "/api/saddle/understanding?project=plain",
|
|
|
+ fake_token)
|
|
|
+ check(status == 200 and body.get("ok") is True
|
|
|
+ and body.get("understanding") is None,
|
|
|
+ "GET answers understanding null for a project with none")
|
|
|
+
|
|
|
+ print("== understanding: rides the assignment payload ==")
|
|
|
+ payload_count = len(fake.start_payloads)
|
|
|
+ create_queued(auth, {
|
|
|
+ "project_id": "1",
|
|
|
+ "title": "Understanding dispatch",
|
|
|
+ "description": "The current understanding rides the /start payload.",
|
|
|
+ "priority": "2",
|
|
|
+ })
|
|
|
+ check(wait_for(lambda: len(fake.start_payloads) > payload_count or False,
|
|
|
+ timeout=30, what="understanding /start push") is not None,
|
|
|
+ "dispatcher pushes the understanding task")
|
|
|
+ payload = fake.start_payloads[-1]
|
|
|
+ check("understanding" in payload,
|
|
|
+ "the assignment payload carries an understanding member")
|
|
|
+ check(payload.get("understanding") == "# Demo understanding\n\nSecond revision.",
|
|
|
+ "the payload's understanding is the project's CURRENT revision")
|
|
|
+
|
|
|
+ # A giant revision is head-clamped at 16000 chars (the payload cap).
|
|
|
+ conn = db()
|
|
|
+ conn.execute("insert into project_understandings"
|
|
|
+ " (project_id, body, commit_hash, task_id, created_at)"
|
|
|
+ " values (1, ?, 'big0000', 0, strftime('%s','now'))",
|
|
|
+ ("giant!" + "y" * 25000,))
|
|
|
+ conn.commit()
|
|
|
+ conn.close()
|
|
|
+ payload_count = len(fake.start_payloads)
|
|
|
+ create_queued(auth, {
|
|
|
+ "project_id": "1",
|
|
|
+ "title": "Understanding cap dispatch",
|
|
|
+ "priority": "2",
|
|
|
+ })
|
|
|
+ check(wait_for(lambda: len(fake.start_payloads) > payload_count or False,
|
|
|
+ timeout=30, what="cap /start push") is not None,
|
|
|
+ "dispatcher pushes the cap task")
|
|
|
+ shipped = fake.start_payloads[-1].get("understanding", "")
|
|
|
+ check(len(shipped) == 16000 and shipped.startswith("giant!"),
|
|
|
+ "the payload caps the understanding at 16000 chars, head kept (%d)"
|
|
|
+ % len(shipped))
|
|
|
+
|
|
|
+ # No understanding yet: the member is present but empty.
|
|
|
+ plain_id = int([p["id"] for p in
|
|
|
+ slot_public(auth, "/projects", "projects")["projects"]
|
|
|
+ if p["name"] == "plain"][0])
|
|
|
+ payload_count = len(fake.start_payloads)
|
|
|
+ create_queued(auth, {
|
|
|
+ "project_id": str(plain_id),
|
|
|
+ "title": "No-understanding dispatch",
|
|
|
+ "priority": "2",
|
|
|
+ })
|
|
|
+ check(wait_for(lambda: len(fake.start_payloads) > payload_count or False,
|
|
|
+ timeout=30, what="empty /start push") is not None,
|
|
|
+ "dispatcher pushes the no-understanding task")
|
|
|
+ check(fake.start_payloads[-1].get("understanding") == "",
|
|
|
+ "a project without an understanding ships an empty string")
|