102 lines
3.3 KiB
Go
102 lines
3.3 KiB
Go
package state
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
"github.com/kandev/kandev/internal/db"
|
|
)
|
|
|
|
func newInstanceStoreTest(t *testing.T) *InstanceStore {
|
|
t.Helper()
|
|
conn, err := sql.Open("sqlite3", filepath.Join(t.TempDir(), "instance-state.db"))
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
conn.SetMaxOpenConns(1)
|
|
pool := db.NewPool(sqlx.NewDb(conn, "sqlite3"), sqlx.NewDb(conn, "sqlite3"))
|
|
t.Cleanup(func() { _ = pool.Close() })
|
|
store, err := NewInstanceStore(pool)
|
|
if err != nil {
|
|
t.Fatalf("NewInstanceStore: %v", err)
|
|
}
|
|
return store
|
|
}
|
|
|
|
func TestInstanceStoreUsesRevisionedConditionalWrites(t *testing.T) {
|
|
store := newInstanceStoreTest(t)
|
|
ctx := context.Background()
|
|
first, err := store.Set(ctx, "instance-1", "filters", json.RawMessage(`{"group":"step"}`), ptrInt64(0), "browser")
|
|
if err != nil {
|
|
t.Fatalf("initial Set: %v", err)
|
|
}
|
|
if first.Revision != 1 {
|
|
t.Fatalf("initial revision = %d, want 1", first.Revision)
|
|
}
|
|
second, err := store.Set(ctx, "instance-1", "filters", json.RawMessage(`{"group":"workflow"}`), ptrInt64(1), "agent")
|
|
if err != nil {
|
|
t.Fatalf("conditional Set: %v", err)
|
|
}
|
|
if second.Revision != 2 {
|
|
t.Fatalf("second revision = %d, want 2", second.Revision)
|
|
}
|
|
_, err = store.Set(ctx, "instance-1", "filters", json.RawMessage(`{"group":"stale"}`), ptrInt64(1), "browser")
|
|
var conflict *ConflictError
|
|
if !errors.As(err, &conflict) || conflict.CurrentRevision != 2 {
|
|
t.Fatalf("stale Set = %v, conflict = %+v", err, conflict)
|
|
}
|
|
got, found, err := store.Get(ctx, "instance-1", "filters")
|
|
if err != nil || !found || string(got.Value) != `{"group":"workflow"}` || got.Revision != 2 {
|
|
t.Fatalf("Get = %+v, found=%v, err=%v", got, found, err)
|
|
}
|
|
}
|
|
|
|
func TestInstanceStoreDeleteRetainsTombstoneRevision(t *testing.T) {
|
|
store := newInstanceStoreTest(t)
|
|
ctx := context.Background()
|
|
if _, err := store.Set(ctx, "instance-1", "draft", json.RawMessage(`1`), ptrInt64(0), "browser"); err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
revision, err := store.Delete(ctx, "instance-1", "draft", ptrInt64(1), "browser")
|
|
if err != nil || revision != 2 {
|
|
t.Fatalf("Delete = %d, %v; want revision 2", revision, err)
|
|
}
|
|
entry, found, err := store.Get(ctx, "instance-1", "draft")
|
|
if err != nil || found || entry.Revision != 2 {
|
|
t.Fatalf("Get tombstone = %+v, found=%v, err=%v", entry, found, err)
|
|
}
|
|
var conflict *ConflictError
|
|
if _, err := store.Set(ctx, "instance-1", "draft", json.RawMessage(`2`), ptrInt64(1), "browser"); !errors.As(err, &conflict) {
|
|
t.Fatalf("stale recreate = %v, want conflict", err)
|
|
}
|
|
}
|
|
|
|
func TestInstanceStoreDeleteInstanceRemovesAllState(t *testing.T) {
|
|
store := newInstanceStoreTest(t)
|
|
ctx := context.Background()
|
|
for _, key := range []string{"one", "two"} {
|
|
if _, err := store.Set(ctx, "instance-1", key, json.RawMessage(`{"ok":true}`), ptrInt64(0), "agent"); err != nil {
|
|
t.Fatalf("Set(%s): %v", key, err)
|
|
}
|
|
}
|
|
if err := store.DeleteInstance(ctx, "instance-1"); err != nil {
|
|
t.Fatalf("DeleteInstance: %v", err)
|
|
}
|
|
entries, err := store.List(ctx, "instance-1")
|
|
if err != nil {
|
|
t.Fatalf("List after DeleteInstance: %v", err)
|
|
}
|
|
if len(entries) != 0 {
|
|
t.Fatalf("state after DeleteInstance = %+v, want empty", entries)
|
|
}
|
|
}
|
|
|
|
func ptrInt64(value int64) *int64 { return &value }
|