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:
@@ -2,10 +2,13 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/jakub/sm-bm-plugin/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -86,3 +89,42 @@ func TestMaterialsAdminHooks(t *testing.T) {
|
||||
t.Errorf("calls for a wrong model = %v", rec.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMaterialsAdminRevision checks the revision hooks of the materials
|
||||
// admin outside a database write: without a transaction they do nothing and
|
||||
// store no snapshot, a model that is not a material is an error, and the
|
||||
// embedded date check still runs first.
|
||||
func TestMaterialsAdminRevision(t *testing.T) {
|
||||
t.Cleanup(func() { UseMaterialIndex(nil) })
|
||||
UseMaterialIndex(nil)
|
||||
var c materialsAdmin
|
||||
ctx := context.Background()
|
||||
m := &models.Material{ID: 41, Title: "Notatka", Type: "notatka"}
|
||||
|
||||
if err := c.FormBeforeUpdate(ctx, m); err != nil {
|
||||
t.Errorf("before update without a transaction: %v", err)
|
||||
}
|
||||
if _, found := pendingRevisions.Load(m.ID); found {
|
||||
t.Error("before update without a transaction stored a snapshot")
|
||||
}
|
||||
if err := c.FormAfterUpdate(ctx, m); err != nil {
|
||||
t.Errorf("after update without a transaction: %v", err)
|
||||
}
|
||||
|
||||
if err := c.FormBeforeUpdate(ctx, &models.Course{ID: 41}); err == nil {
|
||||
t.Error("before update with a course: no error")
|
||||
}
|
||||
var nilMaterial *models.Material
|
||||
if err := c.FormBeforeUpdate(ctx, nilMaterial); err == nil {
|
||||
t.Error("before update with a nil material: no error")
|
||||
}
|
||||
|
||||
at := func(h, m int) time.Time { return time.Date(2026, 10, 3, h, m, 0, 0, models.Location) }
|
||||
bad := &models.ClassSession{StartsAt: at(8, 0), EndsAt: at(7, 0)}
|
||||
var ve *cabana.ValidationError
|
||||
if err := c.FormBeforeUpdate(ctx, bad); !errors.As(err, &ve) {
|
||||
t.Errorf("before update with bad dates: err = %v, want a cabana.ValidationError", err)
|
||||
} else if _, ok := ve.Details["ends_at"]; !ok {
|
||||
t.Errorf("before update with bad dates: details %v name no ends_at", ve.Details)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user