Testing
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/uopi/uopi/internal/storage"
|
||||
)
|
||||
|
||||
// mkVersioned creates an interface and pushes it to version 3 by updating
|
||||
// twice, returning the id. After this the store holds: current (v3) plus
|
||||
// backups id.v1.xml and id.v2.xml.
|
||||
func mkVersioned(t *testing.T) (*storage.Store, string) {
|
||||
t.Helper()
|
||||
s := newStore(t)
|
||||
id, err := s.Create([]byte(`<interface id="vers" name="V1" version="1"><widget/></interface>`), "")
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if err := s.Update(id, []byte(`<interface id="vers" name="V2" version="1"><widget/></interface>`), "second"); err != nil {
|
||||
t.Fatalf("Update→v2: %v", err)
|
||||
}
|
||||
if err := s.Update(id, []byte(`<interface id="vers" name="V3" version="1"><widget/></interface>`), ""); err != nil {
|
||||
t.Fatalf("Update→v3: %v", err)
|
||||
}
|
||||
return s, id
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Groups //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestReadGroupsDefaultsEmptyArray(t *testing.T) {
|
||||
s := newStore(t)
|
||||
data, err := s.ReadGroups()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadGroups: %v", err)
|
||||
}
|
||||
if string(data) != "[]" {
|
||||
t.Errorf("ReadGroups default = %q, want %q", data, "[]")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteThenReadGroups(t *testing.T) {
|
||||
s := newStore(t)
|
||||
want := `[{"id":"a","name":"Area A"}]`
|
||||
if err := s.WriteGroups([]byte(want)); err != nil {
|
||||
t.Fatalf("WriteGroups: %v", err)
|
||||
}
|
||||
got, err := s.ReadGroups()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadGroups: %v", err)
|
||||
}
|
||||
if string(got) != want {
|
||||
t.Errorf("ReadGroups = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Versions //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestVersionsNewestFirstWithCurrentFlag(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
vs, err := s.Versions(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Versions: %v", err)
|
||||
}
|
||||
if len(vs) != 3 {
|
||||
t.Fatalf("expected 3 versions, got %d", len(vs))
|
||||
}
|
||||
if vs[0].Version != 3 || vs[1].Version != 2 || vs[2].Version != 1 {
|
||||
t.Errorf("not newest-first: %d, %d, %d", vs[0].Version, vs[1].Version, vs[2].Version)
|
||||
}
|
||||
if !vs[0].Current {
|
||||
t.Error("v3 should be flagged Current")
|
||||
}
|
||||
if vs[1].Current || vs[2].Current {
|
||||
t.Error("backup revisions must not be flagged Current")
|
||||
}
|
||||
// The "second" tag was stamped on the revision that became backup v2.
|
||||
if vs[1].Tag != "second" {
|
||||
t.Errorf("v2 tag = %q, want %q", vs[1].Tag, "second")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionsNotFound(t *testing.T) {
|
||||
s := newStore(t)
|
||||
if _, err := s.Versions("ghost"); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("Versions missing: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionsInvalidID(t *testing.T) {
|
||||
s := newStore(t)
|
||||
if _, err := s.Versions("../x"); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("Versions bad ID: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// GetVersion //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestGetVersionCurrentAndBackup(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
|
||||
cur, err := s.GetVersion(id, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("GetVersion(3): %v", err)
|
||||
}
|
||||
if !strings.Contains(string(cur), `name="V3"`) {
|
||||
t.Errorf("GetVersion(3) wrong content: %s", cur)
|
||||
}
|
||||
|
||||
old, err := s.GetVersion(id, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetVersion(1): %v", err)
|
||||
}
|
||||
if !strings.Contains(string(old), `name="V1"`) {
|
||||
t.Errorf("GetVersion(1) wrong content: %s", old)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionMissingRevision(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
if _, err := s.GetVersion(id, 99); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("GetVersion(99): want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionMissingInterface(t *testing.T) {
|
||||
s := newStore(t)
|
||||
if _, err := s.GetVersion("ghost", 1); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("GetVersion missing iface: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// SetVersionTag //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestSetVersionTagOnBackupAndClear(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
|
||||
// Tag a backup revision in place (no new revision created).
|
||||
if err := s.SetVersionTag(id, 1, "milestone"); err != nil {
|
||||
t.Fatalf("SetVersionTag(1): %v", err)
|
||||
}
|
||||
vs, _ := s.Versions(id)
|
||||
if len(vs) != 3 {
|
||||
t.Fatalf("tagging must not add a revision: got %d", len(vs))
|
||||
}
|
||||
var v1 *storage.VersionMeta
|
||||
for i := range vs {
|
||||
if vs[i].Version == 1 {
|
||||
v1 = &vs[i]
|
||||
}
|
||||
}
|
||||
if v1 == nil || v1.Tag != "milestone" {
|
||||
t.Fatalf("v1 tag not set: %+v", v1)
|
||||
}
|
||||
|
||||
// Clearing the tag.
|
||||
if err := s.SetVersionTag(id, 1, ""); err != nil {
|
||||
t.Fatalf("SetVersionTag clear: %v", err)
|
||||
}
|
||||
vs, _ = s.Versions(id)
|
||||
for _, v := range vs {
|
||||
if v.Version == 1 && v.Tag != "" {
|
||||
t.Errorf("v1 tag should be cleared, got %q", v.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetVersionTagCurrent(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
if err := s.SetVersionTag(id, 3, "live"); err != nil {
|
||||
t.Fatalf("SetVersionTag(3): %v", err)
|
||||
}
|
||||
vs, _ := s.Versions(id)
|
||||
if vs[0].Version != 3 || vs[0].Tag != "live" {
|
||||
t.Errorf("current tag not applied: %+v", vs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetVersionTagMissing(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
if err := s.SetVersionTag(id, 99, "x"); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("SetVersionTag missing rev: want ErrNotFound, got %v", err)
|
||||
}
|
||||
if err := s.SetVersionTag("../bad", 1, "x"); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("SetVersionTag bad ID: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Promote //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestPromoteIsNonDestructive(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
|
||||
// Promote v1 — its content becomes the new current revision (v4), while
|
||||
// every prior revision is preserved.
|
||||
if err := s.Promote(id, 1); err != nil {
|
||||
t.Fatalf("Promote(1): %v", err)
|
||||
}
|
||||
vs, err := s.Versions(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Versions after promote: %v", err)
|
||||
}
|
||||
if len(vs) != 4 {
|
||||
t.Fatalf("expected 4 versions after promote, got %d", len(vs))
|
||||
}
|
||||
if vs[0].Version != 4 || !vs[0].Current {
|
||||
t.Errorf("new current should be v4: %+v", vs[0])
|
||||
}
|
||||
if vs[0].Name != "V1" {
|
||||
t.Errorf("promoted content should be V1's, got name %q", vs[0].Name)
|
||||
}
|
||||
if vs[0].Tag != "restored from v1" {
|
||||
t.Errorf("promote tag = %q", vs[0].Tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromoteMissing(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
if err := s.Promote(id, 99); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("Promote missing rev: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Fork //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestForkResetsVersionAndClearsTag(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
if err := s.SetVersionTag(id, 2, "tagged"); err != nil {
|
||||
t.Fatalf("SetVersionTag: %v", err)
|
||||
}
|
||||
|
||||
newID, err := s.Fork(id, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("Fork(2): %v", err)
|
||||
}
|
||||
if newID == id {
|
||||
t.Fatal("Fork must produce a distinct ID")
|
||||
}
|
||||
|
||||
data, err := s.Get(newID)
|
||||
if err != nil {
|
||||
t.Fatalf("Get(fork): %v", err)
|
||||
}
|
||||
str := string(data)
|
||||
if !strings.Contains(str, `name="V2"`) {
|
||||
t.Errorf("fork should carry V2 content: %s", str)
|
||||
}
|
||||
if !strings.Contains(str, `version="1"`) {
|
||||
t.Errorf("fork version should reset to 1: %s", str)
|
||||
}
|
||||
if !strings.Contains(str, `id="`+newID+`"`) {
|
||||
t.Errorf("fork id attr should be stamped: %s", str)
|
||||
}
|
||||
if strings.Contains(str, "tagged") {
|
||||
t.Errorf("fork should clear the tag: %s", str)
|
||||
}
|
||||
|
||||
// The fork is an independent, listable interface at v1.
|
||||
vs, err := s.Versions(newID)
|
||||
if err != nil {
|
||||
t.Fatalf("Versions(fork): %v", err)
|
||||
}
|
||||
if len(vs) != 1 || vs[0].Version != 1 {
|
||||
t.Errorf("fork should have a single v1 revision, got %+v", vs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForkMissing(t *testing.T) {
|
||||
s := newStore(t)
|
||||
if _, err := s.Fork("ghost", 1); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("Fork missing: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Create: slug derivation + attribute stamping //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestCreateSlugifiesNameWhenNoID(t *testing.T) {
|
||||
s := newStore(t)
|
||||
id, err := s.Create([]byte(`<interface name="My Cool Panel!" version="1"/>`), "")
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if id != "my-cool-panel" {
|
||||
t.Errorf("slug id = %q, want %q", id, "my-cool-panel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateEmptyNameFallsBackToInterface(t *testing.T) {
|
||||
s := newStore(t)
|
||||
id, err := s.Create([]byte(`<interface name="" version="1"/>`), "")
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if id != "interface" {
|
||||
t.Errorf("fallback id = %q, want %q", id, "interface")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateStampsMissingVersionAndID(t *testing.T) {
|
||||
s := newStore(t)
|
||||
// XML carries neither id nor version attributes — Create must insert both.
|
||||
id, err := s.Create([]byte(`<interface name="Stamp Me"><widget/></interface>`), "label")
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
data, err := s.Get(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
str := string(data)
|
||||
if !strings.Contains(str, `version="1"`) {
|
||||
t.Errorf("Create should stamp version=1: %s", str)
|
||||
}
|
||||
if !strings.Contains(str, `id="`+id+`"`) {
|
||||
t.Errorf("Create should stamp id: %s", str)
|
||||
}
|
||||
if !strings.Contains(str, `tag="label"`) {
|
||||
t.Errorf("Create should stamp tag: %s", str)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Update tagging + backup chain //
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
func TestDeleteMovesVersionedBackups(t *testing.T) {
|
||||
s, id := mkVersioned(t) // current + id.v1.xml + id.v2.xml
|
||||
if err := s.Delete(id); err != nil {
|
||||
t.Fatalf("Delete: %v", err)
|
||||
}
|
||||
if _, err := s.Get(id); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("Get after delete: want ErrNotFound, got %v", err)
|
||||
}
|
||||
if _, err := s.Versions(id); !errors.Is(err, storage.ErrNotFound) {
|
||||
t.Errorf("Versions after delete: want ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSkipsVersionedBackups(t *testing.T) {
|
||||
s, _ := mkVersioned(t) // leaves id.v1.xml and id.v2.xml on disk
|
||||
list, err := s.List()
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("List should skip versioned backups, got %d entries: %+v", len(list), list)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStampsIncrementingVersion(t *testing.T) {
|
||||
s, id := mkVersioned(t)
|
||||
data, err := s.Get(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(data), `version="3"`) {
|
||||
t.Errorf("current should be version 3 after two updates: %s", data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user