Feat v0.7.9 workflow independence (#63)
All checks were successful
CI/CD / detect-changes (push) Successful in 20s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 25s
CI/CD / test-sqlite (push) Successful in 2m51s
CI/CD / test-go-pg (push) Successful in 3m0s
CI/CD / build-and-deploy (push) Successful in 1m26s
All checks were successful
CI/CD / detect-changes (push) Successful in 20s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 25s
CI/CD / test-sqlite (push) Successful in 2m51s
CI/CD / test-go-pg (push) Successful in 3m0s
CI/CD / build-and-deploy (push) Successful in 1m26s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #63.
This commit is contained in:
629
server/store/sqlite/workflows_test.go
Normal file
629
server/store/sqlite/workflows_test.go
Normal file
@@ -0,0 +1,629 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"armature/database"
|
||||
"armature/models"
|
||||
"armature/store"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Setenv("DB_DRIVER", "sqlite")
|
||||
teardown := database.SetupTestDB()
|
||||
code := m.Run()
|
||||
teardown()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func resetDB(t *testing.T) {
|
||||
t.Helper()
|
||||
database.TruncateAll(t)
|
||||
SetDB(database.TestDB)
|
||||
}
|
||||
|
||||
// newWF creates a workflow with required defaults filled in.
|
||||
func newWF(name, slug, createdBy string) *models.Workflow {
|
||||
return &models.Workflow{
|
||||
Name: name,
|
||||
Slug: slug,
|
||||
EntryMode: "public_link",
|
||||
CreatedBy: createdBy,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Workflow CRUD ───────────────────────────────
|
||||
|
||||
func TestWorkflowCreate(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Bug Report", "bug-report", userID)
|
||||
wf.Description = "Report a bug"
|
||||
wf.IsActive = true
|
||||
|
||||
if err := s.Create(ctx, wf); err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if wf.ID == "" {
|
||||
t.Fatal("expected ID to be assigned")
|
||||
}
|
||||
if wf.Version != 1 {
|
||||
t.Fatalf("expected version=1, got %d", wf.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowGetByID(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Test WF", "test-wf", userID)
|
||||
wf.IsActive = true
|
||||
if err := s.Create(ctx, wf); err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
|
||||
got, err := s.GetByID(ctx, wf.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID: %v", err)
|
||||
}
|
||||
if got.Name != "Test WF" {
|
||||
t.Fatalf("expected name 'Test WF', got %q", got.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowGetBySlug(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Slug Test", "slug-test", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
got, err := s.GetBySlug(ctx, nil, "slug-test")
|
||||
if err != nil {
|
||||
t.Fatalf("GetBySlug: %v", err)
|
||||
}
|
||||
if got == nil || got.ID != wf.ID {
|
||||
t.Fatal("expected to find workflow by slug")
|
||||
}
|
||||
|
||||
got, _ = s.GetBySlug(ctx, nil, "nope")
|
||||
if got != nil {
|
||||
t.Fatal("expected nil for non-existent slug")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowUpdate(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Original", "original", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
newName := "Updated"
|
||||
if err := s.Update(ctx, wf.ID, models.WorkflowPatch{Name: &newName}); err != nil {
|
||||
t.Fatalf("Update: %v", err)
|
||||
}
|
||||
|
||||
got, _ := s.GetByID(ctx, wf.ID)
|
||||
if got.Name != "Updated" {
|
||||
t.Fatalf("expected name 'Updated', got %q", got.Name)
|
||||
}
|
||||
if got.Version != 2 {
|
||||
t.Fatalf("expected version=2, got %d", got.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowDelete(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Doomed", "doomed", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
if err := s.Delete(ctx, wf.ID); err != nil {
|
||||
t.Fatalf("Delete: %v", err)
|
||||
}
|
||||
got, _ := s.GetByID(ctx, wf.ID)
|
||||
if got != nil {
|
||||
t.Fatal("expected nil after delete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowListGlobal(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
s.Create(ctx, newWF("A", "a", userID))
|
||||
s.Create(ctx, newWF("B", "b", userID))
|
||||
|
||||
list, err := s.ListGlobal(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ListGlobal: %v", err)
|
||||
}
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("expected 2 workflows, got %d", len(list))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stage CRUD ──────────────────────────────────
|
||||
|
||||
func TestStageCRUD(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Staged", "staged", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
stage1 := &models.WorkflowStage{
|
||||
WorkflowID: wf.ID,
|
||||
Ordinal: 0,
|
||||
Name: "Intake",
|
||||
StageMode: "form",
|
||||
FormTemplate: json.RawMessage(`{"fields":[{"key":"name","type":"text","label":"Name"}]}`),
|
||||
}
|
||||
if err := s.CreateStage(ctx, stage1); err != nil {
|
||||
t.Fatalf("CreateStage: %v", err)
|
||||
}
|
||||
|
||||
stage2 := &models.WorkflowStage{
|
||||
WorkflowID: wf.ID,
|
||||
Ordinal: 1,
|
||||
Name: "Review",
|
||||
StageMode: "review",
|
||||
}
|
||||
s.CreateStage(ctx, stage2)
|
||||
|
||||
stages, err := s.ListStages(ctx, wf.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("ListStages: %v", err)
|
||||
}
|
||||
if len(stages) != 2 {
|
||||
t.Fatalf("expected 2 stages, got %d", len(stages))
|
||||
}
|
||||
if stages[0].Name != "Intake" || stages[0].StageMode != "form" {
|
||||
t.Fatalf("stage 0: got %s/%s", stages[0].Name, stages[0].StageMode)
|
||||
}
|
||||
if stages[1].Name != "Review" || stages[1].StageMode != "review" {
|
||||
t.Fatalf("stage 1: got %s/%s", stages[1].Name, stages[1].StageMode)
|
||||
}
|
||||
|
||||
// Update
|
||||
stage1.Name = "Updated Intake"
|
||||
if err := s.UpdateStage(ctx, stage1); err != nil {
|
||||
t.Fatalf("UpdateStage: %v", err)
|
||||
}
|
||||
stages, _ = s.ListStages(ctx, wf.ID)
|
||||
if stages[0].Name != "Updated Intake" {
|
||||
t.Fatalf("expected updated name, got %q", stages[0].Name)
|
||||
}
|
||||
|
||||
// Delete
|
||||
if err := s.DeleteStage(ctx, stage2.ID); err != nil {
|
||||
t.Fatalf("DeleteStage: %v", err)
|
||||
}
|
||||
stages, _ = s.ListStages(ctx, wf.ID)
|
||||
if len(stages) != 1 {
|
||||
t.Fatalf("expected 1 stage after delete, got %d", len(stages))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageReorder(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Reorder", "reorder", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
s1 := &models.WorkflowStage{WorkflowID: wf.ID, Ordinal: 0, Name: "First", StageMode: "form"}
|
||||
s2 := &models.WorkflowStage{WorkflowID: wf.ID, Ordinal: 1, Name: "Second", StageMode: "review"}
|
||||
s.CreateStage(ctx, s1)
|
||||
s.CreateStage(ctx, s2)
|
||||
|
||||
if err := s.ReorderStages(ctx, wf.ID, []string{s2.ID, s1.ID}); err != nil {
|
||||
t.Fatalf("ReorderStages: %v", err)
|
||||
}
|
||||
stages, _ := s.ListStages(ctx, wf.ID)
|
||||
if stages[0].Name != "Second" || stages[1].Name != "First" {
|
||||
t.Fatal("expected reversed order after reorder")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Instance Lifecycle ──────────────────────────
|
||||
|
||||
func TestInstanceLifecycle(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Lifecycle", "lifecycle", userID)
|
||||
wf.IsActive = true
|
||||
s.Create(ctx, wf)
|
||||
|
||||
stage := &models.WorkflowStage{WorkflowID: wf.ID, Ordinal: 0, Name: "Step 1", StageMode: "form"}
|
||||
s.CreateStage(ctx, stage)
|
||||
|
||||
token := "test-token-123"
|
||||
inst := &models.WorkflowInstance{
|
||||
WorkflowID: wf.ID,
|
||||
WorkflowVersion: 1,
|
||||
CurrentStage: stage.ID,
|
||||
Status: "active",
|
||||
StartedBy: userID,
|
||||
EntryToken: &token,
|
||||
StageData: json.RawMessage(`{}`),
|
||||
Metadata: json.RawMessage(`{}`),
|
||||
}
|
||||
if err := s.CreateInstance(ctx, inst); err != nil {
|
||||
t.Fatalf("CreateInstance: %v", err)
|
||||
}
|
||||
|
||||
// GetInstance
|
||||
got, err := s.GetInstance(ctx, inst.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetInstance: %v", err)
|
||||
}
|
||||
if got.Status != "active" {
|
||||
t.Fatalf("expected 'active', got %q", got.Status)
|
||||
}
|
||||
|
||||
// GetInstanceByToken
|
||||
got, err = s.GetInstanceByToken(ctx, "test-token-123")
|
||||
if err != nil {
|
||||
t.Fatalf("GetInstanceByToken: %v", err)
|
||||
}
|
||||
if got.ID != inst.ID {
|
||||
t.Fatal("token lookup returned wrong instance")
|
||||
}
|
||||
|
||||
// AdvanceStage
|
||||
if err := s.AdvanceStage(ctx, inst.ID, "step-2", json.RawMessage(`{"key":"value"}`)); err != nil {
|
||||
t.Fatalf("AdvanceStage: %v", err)
|
||||
}
|
||||
got, _ = s.GetInstance(ctx, inst.ID)
|
||||
if got.CurrentStage != "step-2" {
|
||||
t.Fatalf("expected 'step-2', got %q", got.CurrentStage)
|
||||
}
|
||||
|
||||
// CompleteInstance
|
||||
if err := s.CompleteInstance(ctx, inst.ID); err != nil {
|
||||
t.Fatalf("CompleteInstance: %v", err)
|
||||
}
|
||||
got, _ = s.GetInstance(ctx, inst.ID)
|
||||
if got.Status != "completed" {
|
||||
t.Fatalf("expected 'completed', got %q", got.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceCancel(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Cancel", "cancel", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
inst := &models.WorkflowInstance{
|
||||
WorkflowID: wf.ID, WorkflowVersion: 1, CurrentStage: "s1",
|
||||
Status: "active", StartedBy: userID,
|
||||
StageData: json.RawMessage(`{}`), Metadata: json.RawMessage(`{}`),
|
||||
}
|
||||
s.CreateInstance(ctx, inst)
|
||||
|
||||
if err := s.CancelInstance(ctx, inst.ID); err != nil {
|
||||
t.Fatalf("CancelInstance: %v", err)
|
||||
}
|
||||
got, _ := s.GetInstance(ctx, inst.ID)
|
||||
if got.Status != "cancelled" {
|
||||
t.Fatalf("expected 'cancelled', got %q", got.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceStale(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("Stale", "stale", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
inst := &models.WorkflowInstance{
|
||||
WorkflowID: wf.ID, WorkflowVersion: 1, CurrentStage: "s1",
|
||||
Status: "active", StartedBy: userID,
|
||||
StageData: json.RawMessage(`{}`), Metadata: json.RawMessage(`{}`),
|
||||
}
|
||||
s.CreateInstance(ctx, inst)
|
||||
|
||||
if err := s.MarkInstanceStale(ctx, inst.ID); err != nil {
|
||||
t.Fatalf("MarkInstanceStale: %v", err)
|
||||
}
|
||||
got, _ := s.GetInstance(ctx, inst.ID)
|
||||
if got.Status != "stale" {
|
||||
t.Fatalf("expected 'stale', got %q", got.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListInstances(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
wf := newWF("List", "list", userID)
|
||||
s.Create(ctx, wf)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
inst := &models.WorkflowInstance{
|
||||
WorkflowID: wf.ID, WorkflowVersion: 1, CurrentStage: "s1",
|
||||
Status: "active", StartedBy: userID,
|
||||
StageData: json.RawMessage(`{}`), Metadata: json.RawMessage(`{}`),
|
||||
}
|
||||
s.CreateInstance(ctx, inst)
|
||||
}
|
||||
|
||||
list, err := s.ListInstances(ctx, wf.ID, "", store.ListOptions{Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("ListInstances: %v", err)
|
||||
}
|
||||
if len(list) != 3 {
|
||||
t.Fatalf("expected 3, got %d", len(list))
|
||||
}
|
||||
|
||||
list, _ = s.ListInstances(ctx, wf.ID, "completed", store.ListOptions{Limit: 10})
|
||||
if len(list) != 0 {
|
||||
t.Fatalf("expected 0 completed, got %d", len(list))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Team-scoped ─────────────────────────────────
|
||||
|
||||
func TestWorkflowTeamScope(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewWorkflowStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "wfuser", "wfuser@test.com")
|
||||
teamID := database.SeedTestTeam(t, "TestTeam", userID)
|
||||
|
||||
wf := newWF("Team WF", "team-wf", userID)
|
||||
wf.TeamID = &teamID
|
||||
s.Create(ctx, wf)
|
||||
|
||||
got, err := s.GetBySlug(ctx, &teamID, "team-wf")
|
||||
if err != nil || got == nil {
|
||||
t.Fatalf("GetBySlug(team): %v", err)
|
||||
}
|
||||
if got.ID != wf.ID {
|
||||
t.Fatal("wrong workflow")
|
||||
}
|
||||
|
||||
list, _ := s.ListForTeam(ctx, teamID)
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("expected 1 team workflow, got %d", len(list))
|
||||
}
|
||||
|
||||
global, _ := s.ListGlobal(ctx)
|
||||
if len(global) != 0 {
|
||||
t.Fatalf("expected 0 global workflows, got %d", len(global))
|
||||
}
|
||||
}
|
||||
|
||||
// ── API Token Store ─────────────────────────────
|
||||
|
||||
func TestAPITokenCRUD(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewAPITokenStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "tokenuser", "tokenuser@test.com")
|
||||
|
||||
tok := &models.APIToken{
|
||||
UserID: userID,
|
||||
Name: "CI Token",
|
||||
TokenHash: "sha256_abc123",
|
||||
Permissions: []string{"extension.use"},
|
||||
}
|
||||
if err := s.Create(ctx, tok); err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if tok.ID == "" {
|
||||
t.Fatal("expected ID")
|
||||
}
|
||||
|
||||
// ListForUser
|
||||
list, err := s.ListForUser(ctx, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("ListForUser: %v", err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("expected 1 token, got %d", len(list))
|
||||
}
|
||||
if list[0].Name != "CI Token" {
|
||||
t.Fatalf("expected 'CI Token', got %q", list[0].Name)
|
||||
}
|
||||
|
||||
// GetByHash
|
||||
got, err := s.GetByHash(ctx, "sha256_abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("GetByHash: %v", err)
|
||||
}
|
||||
if got.ID != tok.ID {
|
||||
t.Fatal("hash lookup returned wrong token")
|
||||
}
|
||||
|
||||
// Revoke
|
||||
if _, err := s.Revoke(ctx, tok.ID, userID); err != nil {
|
||||
t.Fatalf("Revoke: %v", err)
|
||||
}
|
||||
list, _ = s.ListForUser(ctx, userID)
|
||||
if len(list) != 0 {
|
||||
t.Fatalf("expected 0 tokens after revoke, got %d", len(list))
|
||||
}
|
||||
}
|
||||
|
||||
// ── User Store ──────────────────────────────────
|
||||
|
||||
func TestUserGetByID(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewUserStore()
|
||||
ctx := context.Background()
|
||||
|
||||
u := &models.User{
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "$2a$10$dummy",
|
||||
DisplayName: "Test User",
|
||||
IsActive: true,
|
||||
}
|
||||
if err := s.Create(ctx, u); err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
|
||||
got, err := s.GetByID(ctx, u.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID: %v", err)
|
||||
}
|
||||
if got.Username != "testuser" {
|
||||
t.Fatalf("expected 'testuser', got %q", got.Username)
|
||||
}
|
||||
if got.DisplayName != "Test User" {
|
||||
t.Fatalf("expected 'Test User', got %q", got.DisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserGetByLogin(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewUserStore()
|
||||
ctx := context.Background()
|
||||
|
||||
u := &models.User{
|
||||
Username: "logintest",
|
||||
Email: "login@test.com",
|
||||
PasswordHash: "$2a$10$dummy",
|
||||
IsActive: true,
|
||||
}
|
||||
s.Create(ctx, u)
|
||||
|
||||
// By username
|
||||
got, err := s.GetByLogin(ctx, "logintest")
|
||||
if err != nil {
|
||||
t.Fatalf("GetByLogin(username): %v", err)
|
||||
}
|
||||
if got.ID != u.ID {
|
||||
t.Fatal("wrong user by username")
|
||||
}
|
||||
|
||||
// By email
|
||||
got, err = s.GetByLogin(ctx, "login@test.com")
|
||||
if err != nil {
|
||||
t.Fatalf("GetByLogin(email): %v", err)
|
||||
}
|
||||
if got.ID != u.ID {
|
||||
t.Fatal("wrong user by email")
|
||||
}
|
||||
|
||||
// Case insensitive
|
||||
got, _ = s.GetByLogin(ctx, "LOGINTEST")
|
||||
if got == nil || got.ID != u.ID {
|
||||
t.Fatal("expected case-insensitive match")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Group Store ─────────────────────────────────
|
||||
|
||||
func TestGroupCRUD(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
resetDB(t)
|
||||
s := NewGroupStore()
|
||||
ctx := context.Background()
|
||||
|
||||
userID := database.SeedTestUser(t, "grpuser", "grpuser@test.com")
|
||||
|
||||
g := &models.Group{
|
||||
Name: "Editors",
|
||||
Description: "Can edit content",
|
||||
Scope: "global",
|
||||
CreatedBy: &userID,
|
||||
Permissions: []string{"extension.use", "workflow.create"},
|
||||
}
|
||||
if err := s.Create(ctx, g); err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
|
||||
// GetByID
|
||||
got, err := s.GetByID(ctx, g.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID: %v", err)
|
||||
}
|
||||
if got.Name != "Editors" {
|
||||
t.Fatalf("expected 'Editors', got %q", got.Name)
|
||||
}
|
||||
|
||||
// ListAll (includes system groups from TruncateAll re-seed + our new group)
|
||||
list, err := s.ListAll(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ListAll: %v", err)
|
||||
}
|
||||
if len(list) < 3 { // Everyone + Admins + Editors
|
||||
t.Fatalf("expected ≥3 groups, got %d", len(list))
|
||||
}
|
||||
|
||||
// AddMember / ListMembers
|
||||
if err := s.AddMember(ctx, g.ID, userID, userID); err != nil {
|
||||
t.Fatalf("AddMember: %v", err)
|
||||
}
|
||||
members, err := s.ListMembers(ctx, g.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("ListMembers: %v", err)
|
||||
}
|
||||
if len(members) != 1 {
|
||||
t.Fatalf("expected 1 member, got %d", len(members))
|
||||
}
|
||||
|
||||
// RemoveMember
|
||||
if err := s.RemoveMember(ctx, g.ID, userID); err != nil {
|
||||
t.Fatalf("RemoveMember: %v", err)
|
||||
}
|
||||
members, _ = s.ListMembers(ctx, g.ID)
|
||||
if len(members) != 0 {
|
||||
t.Fatalf("expected 0 members after remove, got %d", len(members))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user