|
@@ -146,6 +146,11 @@ def task_state(auth, task_id):
|
|
|
return slot_public(auth, "/tasks/%d" % task_id, "task")
|
|
return slot_public(auth, "/tasks/%d" % task_id, "task")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def wait_task_done(auth, task_id, timeout=90):
|
|
|
|
|
+ return wait_for(lambda: task_state(auth, task_id).get("status") == "done" or False,
|
|
|
|
|
+ timeout=timeout, what="task %d -> done" % task_id)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def saddle_api(method, path, token, payload=None):
|
|
def saddle_api(method, path, token, payload=None):
|
|
|
"""Raw call against the saddle ingest API; returns (status, json)."""
|
|
"""Raw call against the saddle ingest API; returns (status, json)."""
|
|
|
request = urllib.request.Request(WEB + path,
|
|
request = urllib.request.Request(WEB + path,
|
|
@@ -194,6 +199,7 @@ class FakeSaddle:
|
|
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
class Handler(BaseHTTPRequestHandler):
|
|
|
daemon = None # set on the class before serving
|
|
daemon = None # set on the class before serving
|
|
|
|
|
+ protocol_version = "HTTP/1.1"
|
|
|
|
|
|
|
|
def log_message(self, fmt, *args):
|
|
def log_message(self, fmt, *args):
|
|
|
pass
|
|
pass
|
|
@@ -246,6 +252,7 @@ class FakeSaddle:
|
|
|
self._send(200, {"chunks": chunks, "next": nxt})
|
|
self._send(200, {"chunks": chunks, "next": nxt})
|
|
|
else:
|
|
else:
|
|
|
self._send(404, {"error": "no such route"})
|
|
self._send(404, {"error": "no such route"})
|
|
|
|
|
+ self.close_connection = True
|
|
|
|
|
|
|
|
def serve(self):
|
|
def serve(self):
|
|
|
handler = type("BoundHandler", (FakeSaddle.Handler,), {"daemon": self})
|
|
handler = type("BoundHandler", (FakeSaddle.Handler,), {"daemon": self})
|
|
@@ -359,6 +366,90 @@ def main():
|
|
|
rfc = [b for b in detail["blocks"] if b["block_type"] == "rfc"][0]
|
|
rfc = [b for b in detail["blocks"] if b["block_type"] == "rfc"][0]
|
|
|
check(rfc.get("show_body") is True,
|
|
check(rfc.get("show_body") is True,
|
|
|
"answered plan review shows the body again (review section gone)")
|
|
"answered plan review shows the body again (review section gone)")
|
|
|
|
|
+
|
|
|
|
|
+ print("== feature 5: rules ==")
|
|
|
|
|
+ rules_state = slot_public(auth, "/rules", "rules")
|
|
|
|
|
+ for name, body in [
|
|
|
|
|
+ ("Use Invercargill", "Never use libgee; use Invercargill enumerables."),
|
|
|
|
|
+ ("Write Vala docs", "Every public class carries a doc comment."),
|
|
|
|
|
+ ("Prefer meson", "Builds go through meson; no makefiles."),
|
|
|
|
|
+ ("Co-dev rule", "Keep the wire contract exactly."),
|
|
|
|
|
+ ("Reference rule", "Must never ride along."),
|
|
|
|
|
+ ]:
|
|
|
|
|
+ act(auth, rules_state["create"], {"name": name, "body": body})
|
|
|
|
|
+ listed = {r["name"]: r for r in slot_public(auth, "/rules", "rules")["rules"]}
|
|
|
|
|
+ check(len(listed) == 5, "five rules created and listed on /rules")
|
|
|
|
|
+
|
|
|
|
|
+ def rule_id(name):
|
|
|
|
|
+ rows = [r for r in slot_public(auth, "/rules", "rules")["rules"] if r["name"] == name]
|
|
|
|
|
+ return int(rows[0]["id"])
|
|
|
|
|
+
|
|
|
|
|
+ state = slot_public(auth, "/projects", "projects")
|
|
|
|
|
+ act(auth, state["create"], {"name": "co", "git_url": "https://example.com/co.git"})
|
|
|
|
|
+ act(auth, state["create"], {"name": "ref", "git_url": "https://example.com/ref.git"})
|
|
|
|
|
+ co_id = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
|
|
|
|
|
+ if p["name"] == "co"][0])
|
|
|
|
|
+ ref_id = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
|
|
|
|
|
+ if p["name"] == "ref"][0])
|
|
|
|
|
+
|
|
|
|
|
+ project = slot_public(auth, "/projects/1", "project")
|
|
|
|
|
+ act(auth, project["add_link"], {"linked_project_id": str(co_id),
|
|
|
|
|
+ "relationship": "co-developed"})
|
|
|
|
|
+ act(auth, project["add_link"], {"linked_project_id": str(ref_id),
|
|
|
|
|
+ "relationship": "reference"})
|
|
|
|
|
+ act(auth, project["attach_rule"], {"rule_id": str(rule_id("Use Invercargill"))})
|
|
|
|
|
+ act(auth, project["attach_rule"], {"rule_id": str(rule_id("Prefer meson"))})
|
|
|
|
|
+ project = slot_public(auth, "/projects/1", "project")
|
|
|
|
|
+ check(sorted(r["name"] for r in project["rules"]) == ["Prefer meson", "Use Invercargill"],
|
|
|
|
|
+ "project page lists its attached rules")
|
|
|
|
|
+
|
|
|
|
|
+ co_project = slot_public(auth, "/projects/%d" % co_id, "project")
|
|
|
|
|
+ act(auth, co_project["attach_rule"], {"rule_id": str(rule_id("Co-dev rule"))})
|
|
|
|
|
+ ref_project = slot_public(auth, "/projects/%d" % ref_id, "project")
|
|
|
|
|
+ act(auth, ref_project["attach_rule"], {"rule_id": str(rule_id("Reference rule"))})
|
|
|
|
|
+
|
|
|
|
|
+ tasks_state = slot_public(auth, "/tasks", "tasks")
|
|
|
|
|
+ act(auth, tasks_state["create"], {
|
|
|
|
|
+ "project_id": "1",
|
|
|
|
|
+ "title": "Rules dispatch",
|
|
|
|
|
+ "description": "Effective rule set rides the /start payload.",
|
|
|
|
|
+ "priority": "2",
|
|
|
|
|
+ })
|
|
|
|
|
+ rule_task = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
|
|
|
|
|
+ if t["title"] == "Rules dispatch"][0])
|
|
|
|
|
+ detail = task_state(auth, rule_task)
|
|
|
|
|
+ act(auth, detail["attach_rule"], {"rule_id": str(rule_id("Write Vala docs"))})
|
|
|
|
|
+ act(auth, detail["attach_rule"], {"rule_id": str(rule_id("Use Invercargill"))})
|
|
|
|
|
+ detail = task_state(auth, rule_task)
|
|
|
|
|
+ check(sorted(r["name"] for r in detail["rules"]) == ["Use Invercargill", "Write Vala docs"],
|
|
|
|
|
+ "task page lists its attached rules")
|
|
|
|
|
+
|
|
|
|
|
+ check(wait_for(lambda: len(fake.start_payloads) > 0 or False,
|
|
|
|
|
+ timeout=30, what="/start push") is not None,
|
|
|
|
|
+ "dispatcher pushes the task to the fake saddle")
|
|
|
|
|
+ payload = fake.start_payloads[-1]
|
|
|
|
|
+ rule_names = [r["name"] for r in payload.get("rules", [])]
|
|
|
|
|
+ check(rule_names == ["Write Vala docs", "Use Invercargill", "Prefer meson", "Co-dev rule"],
|
|
|
|
|
+ "effective rules in /start: task, primary, co-developed order (%s)" % rule_names)
|
|
|
|
|
+ check(all(r.get("body") for r in payload.get("rules", [])),
|
|
|
|
|
+ "rules carry their bodies")
|
|
|
|
|
+ check("Reference rule" not in rule_names,
|
|
|
|
|
+ "reference project rules excluded")
|
|
|
|
|
+
|
|
|
|
|
+ detail = task_state(auth, rule_task)
|
|
|
|
|
+ act(auth, detail["rules"][0]["detach"], {})
|
|
|
|
|
+ check([r["name"] for r in task_state(auth, rule_task)["rules"]] == ["Use Invercargill"],
|
|
|
|
|
+ "detached task rule disappears")
|
|
|
|
|
+
|
|
|
|
|
+ rules_rows = slot_public(auth, "/rules", "rules")["rules"]
|
|
|
|
|
+ docs_row = [r for r in rules_rows if r["name"] == "Write Vala docs"][0]
|
|
|
|
|
+ act(auth, docs_row["update"], {"name": "Write Vala docs", "body": "Doc comments or bust."})
|
|
|
|
|
+ check(any(r["body"] == "Doc comments or bust."
|
|
|
|
|
+ for r in slot_public(auth, "/rules", "rules")["rules"]),
|
|
|
|
|
+ "rule edit saves the body")
|
|
|
|
|
+
|
|
|
|
|
+ check(wait_task_done(auth, rule_task, timeout=60) is not None,
|
|
|
|
|
+ "dispatched task completes against the fake daemon")
|
|
|
finally:
|
|
finally:
|
|
|
app.terminate()
|
|
app.terminate()
|
|
|
try:
|
|
try:
|