- 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
93 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|