feat: revision-check the upsert, MCP and admin writes

- POST /materials locks the matched row and answers 409 to a stale
  revision when it updates; a create ignores revision
- bm_manage_save_material and bm_manage_update_material take revision
- /bmadmin saves bump the revision on content changes (not checked)
- document revisions and the 409 in the README
This commit is contained in:
Jakub Zych
2026-11-08 23:36:17 +01:00
parent 237069c827
commit 263c9c3926
7 changed files with 189 additions and 19 deletions

View File

@@ -178,4 +178,39 @@ RETURNING id`).Scan(&groupID).Error; err != nil {
if detail["revision"] != float64(5) {
t.Errorf("student detail revision = %v, want 5", detail["revision"])
}
// POST upserts on title and course: updating the existing material it
// checks a sent revision like PUT; creating one it ignores it.
const upsert = "/api/v1/bm/materials"
code, out = send(http.MethodPost, upsert, with(map[string]any{"body": "nieaktualna\n", "revision": 4}))
if code != http.StatusConflict || out["revision"] != float64(5) {
t.Fatalf("POST with a stale revision = %d %v, want 409 and revision 5", code, out)
}
if err := db.First(&stored, created["id"]).Error; err != nil {
t.Fatal(err)
}
if stored.Body == nil || *stored.Body != "trzecia\n" || stored.Revision != 5 {
t.Errorf("after the POST 409 the row is body %v revision %d, want unchanged", stored.Body, stored.Revision)
}
code, out = send(http.MethodPost, upsert, with(map[string]any{"revision": 5}))
if code != http.StatusOK || out["created"] != false || data(out)["revision"] != float64(5) {
t.Errorf("POST with the current revision and the same content = %d %v, want 200 and revision 5", code, out)
}
code, out = send(http.MethodPost, upsert, with(map[string]any{"body": "czwarta\n", "revision": 5}))
if code != http.StatusOK || out["created"] != false || data(out)["revision"] != float64(6) {
t.Fatalf("POST with the current revision and a new body = %d %v, want 200 and revision 6", code, out)
}
note["body"] = "czwarta\n"
code, out = send(http.MethodPost, upsert, with(map[string]any{"is_private": false}))
if code != http.StatusOK || data(out)["revision"] != float64(6) {
t.Errorf("POST without revision changing only is_private = %d %v, want revision 6", code, out)
}
code, out = send(http.MethodPost, upsert, with(map[string]any{"body": "piąta\n"}))
if code != http.StatusOK || data(out)["revision"] != float64(7) {
t.Errorf("POST without revision and a new body = %d %v, want 200 and revision 7", code, out)
}
code, out = send(http.MethodPost, upsert, map[string]any{"title": "Nowa z rewizją", "type": "notatka", "body": "nowa\n", "revision": 9})
if code != http.StatusCreated || out["created"] != true || data(out)["revision"] != float64(1) {
t.Errorf("POST creating with a revision = %d %v, want 201 and revision 1", code, out)
}
}