Files
sm-bm-plugin/controllers/api/material_revision_test.go
Jakub Zych 237069c827 feat: revision-check material writes
- add jz_bm_materials.revision (default 1), read-only for GORM
- bump it only on content changes (models.ReviseMaterial)
- PUT /materials/{id} locks the row and answers 409 to a stale revision
- management answers carry revision and updated_at, the student detail
  carries revision
2026-11-08 23:33:34 +01:00

93 lines
2.8 KiB
Go

package api
import (
"encoding/json"
"testing"
"time"
"git.golem15.com/jakub/sm-bm-plugin/models"
)
// TestDecodeMaterialRevision checks that revision is an optional key of the
// material body: sent, it decodes to a pointer; absent, to nil; and unknown
// keys are still refused.
func TestDecodeMaterialRevision(t *testing.T) {
d, err := decodeMaterial([]byte(`{"title":"Notatka","type":"notatka","revision":3}`))
if err != nil {
t.Fatal(err)
}
if d.in.Revision == nil || *d.in.Revision != 3 {
t.Errorf("revision = %v, want a pointer to 3", d.in.Revision)
}
d, err = decodeMaterial([]byte(`{"title":"Notatka","type":"notatka","meeting_id":null}`))
if err != nil {
t.Fatal(err)
}
if d.in.Revision != nil {
t.Errorf("absent revision = %v, want nil", *d.in.Revision)
}
if !d.hasMeetingID || d.hasPublishedAt || d.hasAuthor {
t.Errorf("key flags = meeting %v published %v author %v, want only meeting", d.hasMeetingID, d.hasPublishedAt, d.hasAuthor)
}
if _, err := decodeMaterial([]byte(`{"title":"Notatka","type":"notatka","rev":3}`)); err == nil {
t.Error("an unknown key was accepted")
}
}
// TestStaleRevision checks that only a sent revision different from the
// stored one is stale.
func TestStaleRevision(t *testing.T) {
one, two := uint(1), uint(2)
for _, tc := range []struct {
sent *uint
stored uint
want bool
}{
{nil, 1, false},
{nil, 7, false},
{&one, 1, false},
{&two, 1, true},
{&one, 2, true},
} {
if got := staleRevision(tc.sent, tc.stored); got != tc.want {
t.Errorf("staleRevision(%v, %d) = %v, want %v", tc.sent, tc.stored, got, tc.want)
}
}
}
// TestManagedMaterialJSONCarriesRevision checks that a managed material
// carries revision and updated_at.
func TestManagedMaterialJSONCarriesRevision(t *testing.T) {
m := models.Material{ID: 3, Title: "Notatka", Type: "notatka", Revision: 4, UpdatedAt: time.Date(2026, 11, 9, 12, 0, 0, 0, time.UTC)}
b, err := json.Marshal(newManagedMaterialJSON(m, materialPeople{}))
if err != nil {
t.Fatal(err)
}
var keys map[string]any
if err := json.Unmarshal(b, &keys); err != nil {
t.Fatal(err)
}
if keys["revision"] != float64(4) {
t.Errorf("revision = %v in %s, want 4", keys["revision"], b)
}
if keys["updated_at"] != models.FormatDateTime(m.UpdatedAt) {
t.Errorf("updated_at = %v in %s, want %s", keys["updated_at"], b, models.FormatDateTime(m.UpdatedAt))
}
}
// TestMaterialJSONCarriesRevision checks that the student material detail
// carries revision.
func TestMaterialJSONCarriesRevision(t *testing.T) {
b, err := json.Marshal(materialDetailJSON{materialListJSON: materialListJSON{ID: 1, Title: "Notatka"}, Revision: 5})
if err != nil {
t.Fatal(err)
}
var keys map[string]any
if err := json.Unmarshal(b, &keys); err != nil {
t.Fatal(err)
}
if keys["revision"] != float64(5) {
t.Errorf("revision = %v in %s, want 5", keys["revision"], b)
}
}