build source
This commit is contained in:
commit
ee1fec43ed
4171 changed files with 1351288 additions and 0 deletions
385
internal/store/runners_test.go
Normal file
385
internal/store/runners_test.go
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/model"
|
||||
)
|
||||
|
||||
func createTestUser(t *testing.T, s *Store, id string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
_, err := s.CreateUser(ctx, id, model.DefaultQuota())
|
||||
if err != nil {
|
||||
t.Fatalf("create test user %q: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestRunner(user, id string) *model.Runner {
|
||||
return &model.Runner{
|
||||
ID: id,
|
||||
User: user,
|
||||
RepoURL: "ilia/test-repo",
|
||||
Branch: "main",
|
||||
Tools: "go",
|
||||
Task: "implement feature",
|
||||
Status: model.RunnerStatusReceived,
|
||||
CPUReq: "2",
|
||||
MemReq: "4Gi",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
func cleanRunners(t *testing.T) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
if _, err := testPool.Exec(ctx, "DELETE FROM runners"); err != nil {
|
||||
t.Fatalf("clean runners: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRunner(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-abc123")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create runner: %v", err)
|
||||
}
|
||||
|
||||
got, err := s.GetRunner(ctx, "runner-abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("get runner: %v", err)
|
||||
}
|
||||
if got.ID != "runner-abc123" {
|
||||
t.Errorf("got id %q, want %q", got.ID, "runner-abc123")
|
||||
}
|
||||
if got.User != "alice" {
|
||||
t.Errorf("got user %q, want %q", got.User, "alice")
|
||||
}
|
||||
if got.Status != model.RunnerStatusReceived {
|
||||
t.Errorf("got status %q, want %q", got.Status, model.RunnerStatusReceived)
|
||||
}
|
||||
if got.RepoURL != "ilia/test-repo" {
|
||||
t.Errorf("got repo %q, want %q", got.RepoURL, "ilia/test-repo")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRunner_Duplicate(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-dup1")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("first create: %v", err)
|
||||
}
|
||||
err := s.CreateRunner(ctx, r)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for duplicate runner")
|
||||
}
|
||||
if !errors.Is(err, ErrDuplicate) {
|
||||
t.Errorf("got %v, want ErrDuplicate", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRunner_NotFound(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := s.GetRunner(ctx, "nonexistent")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nonexistent runner")
|
||||
}
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("got %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRunners(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
createTestUser(t, s, "bob")
|
||||
ctx := context.Background()
|
||||
|
||||
r1 := newTestRunner("alice", "runner-list1")
|
||||
r2 := newTestRunner("alice", "runner-list2")
|
||||
r3 := newTestRunner("bob", "runner-list3")
|
||||
for _, r := range []*model.Runner{r1, r2, r3} {
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create runner %s: %v", r.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
all, err := s.ListRunners(ctx, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("list all: %v", err)
|
||||
}
|
||||
if len(all) != 3 {
|
||||
t.Fatalf("expected 3 runners, got %d", len(all))
|
||||
}
|
||||
|
||||
aliceRunners, err := s.ListRunners(ctx, "alice", "")
|
||||
if err != nil {
|
||||
t.Fatalf("list alice: %v", err)
|
||||
}
|
||||
if len(aliceRunners) != 2 {
|
||||
t.Fatalf("expected 2 runners for alice, got %d", len(aliceRunners))
|
||||
}
|
||||
|
||||
byStatus, err := s.ListRunners(ctx, "", "received")
|
||||
if err != nil {
|
||||
t.Fatalf("list by status: %v", err)
|
||||
}
|
||||
if len(byStatus) != 3 {
|
||||
t.Fatalf("expected 3 received runners, got %d", len(byStatus))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRunnerStatus_ValidTransition(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-trans1")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
if err := s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusPodCreating, ""); err != nil {
|
||||
t.Fatalf("transition to pod_creating: %v", err)
|
||||
}
|
||||
|
||||
got, _ := s.GetRunner(ctx, r.ID)
|
||||
if got.Status != model.RunnerStatusPodCreating {
|
||||
t.Errorf("expected pod_creating, got %s", got.Status)
|
||||
}
|
||||
|
||||
if err := s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusRunnerRegistered, "forgejo-42"); err != nil {
|
||||
t.Fatalf("transition to runner_registered: %v", err)
|
||||
}
|
||||
|
||||
got, _ = s.GetRunner(ctx, r.ID)
|
||||
if got.Status != model.RunnerStatusRunnerRegistered {
|
||||
t.Errorf("expected runner_registered, got %s", got.Status)
|
||||
}
|
||||
if got.ForgejoRunnerID != "forgejo-42" {
|
||||
t.Errorf("expected forgejo_runner_id 'forgejo-42', got %q", got.ForgejoRunnerID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRunnerStatus_InvalidTransition(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-invalid1")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
err := s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusCompleted, "")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid transition received -> completed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRunnerStatus_SetsClaimedAt(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-claimed1")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusPodCreating, "")
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusRunnerRegistered, "")
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusJobClaimed, "")
|
||||
|
||||
got, _ := s.GetRunner(ctx, r.ID)
|
||||
if got.ClaimedAt == nil {
|
||||
t.Error("expected claimed_at to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRunnerStatus_SetsCompletedAt(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-complete1")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusPodCreating, "")
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusRunnerRegistered, "")
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusJobClaimed, "")
|
||||
_ = s.UpdateRunnerStatus(ctx, r.ID, model.RunnerStatusCompleted, "")
|
||||
|
||||
got, _ := s.GetRunner(ctx, r.ID)
|
||||
if got.CompletedAt == nil {
|
||||
t.Error("expected completed_at to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRunner(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r := newTestRunner("alice", "runner-del1")
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
if err := s.DeleteRunner(ctx, r.ID); err != nil {
|
||||
t.Fatalf("delete: %v", err)
|
||||
}
|
||||
|
||||
_, err := s.GetRunner(ctx, r.ID)
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("expected ErrNotFound after delete, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRunner_NotFound(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
ctx := context.Background()
|
||||
|
||||
err := s.DeleteRunner(ctx, "nonexistent")
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("expected ErrNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDeliveryProcessed(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
isDupe, err := s.IsDeliveryProcessed(ctx, "delivery-1")
|
||||
if err != nil {
|
||||
t.Fatalf("check delivery: %v", err)
|
||||
}
|
||||
if isDupe {
|
||||
t.Error("expected delivery-1 to not be processed yet")
|
||||
}
|
||||
|
||||
r := newTestRunner("alice", "runner-dedupe1")
|
||||
r.WebhookDeliveryID = "delivery-1"
|
||||
if err := s.CreateRunner(ctx, r); err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
isDupe, err = s.IsDeliveryProcessed(ctx, "delivery-1")
|
||||
if err != nil {
|
||||
t.Fatalf("check delivery after create: %v", err)
|
||||
}
|
||||
if !isDupe {
|
||||
t.Error("expected delivery-1 to be processed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDeliveryProcessed_EmptyID(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
ctx := context.Background()
|
||||
|
||||
isDupe, err := s.IsDeliveryProcessed(ctx, "")
|
||||
if err != nil {
|
||||
t.Fatalf("check empty delivery: %v", err)
|
||||
}
|
||||
if isDupe {
|
||||
t.Error("expected empty delivery ID to return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookDeliveryDedupe_UniqueConstraint(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
r1 := newTestRunner("alice", "runner-uniq1")
|
||||
r1.WebhookDeliveryID = "webhook-abc"
|
||||
if err := s.CreateRunner(ctx, r1); err != nil {
|
||||
t.Fatalf("first insert: %v", err)
|
||||
}
|
||||
|
||||
r2 := newTestRunner("alice", "runner-uniq2")
|
||||
r2.WebhookDeliveryID = "webhook-abc"
|
||||
err := s.CreateRunner(ctx, r2)
|
||||
if err == nil {
|
||||
t.Fatal("expected duplicate error for same webhook_delivery_id")
|
||||
}
|
||||
if !errors.Is(err, ErrDuplicate) {
|
||||
t.Errorf("expected ErrDuplicate, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStaleRunners(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
cleanRunners(t)
|
||||
createTestUser(t, s, "alice")
|
||||
ctx := context.Background()
|
||||
|
||||
old := newTestRunner("alice", "runner-stale1")
|
||||
old.CreatedAt = time.Now().UTC().Add(-3 * time.Hour)
|
||||
if err := s.CreateRunner(ctx, old); err != nil {
|
||||
t.Fatalf("create old runner: %v", err)
|
||||
}
|
||||
|
||||
fresh := newTestRunner("alice", "runner-fresh1")
|
||||
fresh.CreatedAt = time.Now().UTC()
|
||||
if err := s.CreateRunner(ctx, fresh); err != nil {
|
||||
t.Fatalf("create fresh runner: %v", err)
|
||||
}
|
||||
|
||||
stale, err := s.GetStaleRunners(ctx, 2*time.Hour)
|
||||
if err != nil {
|
||||
t.Fatalf("get stale: %v", err)
|
||||
}
|
||||
if len(stale) != 1 {
|
||||
t.Fatalf("expected 1 stale runner, got %d", len(stale))
|
||||
}
|
||||
if stale[0].ID != "runner-stale1" {
|
||||
t.Errorf("expected stale runner runner-stale1, got %s", stale[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRunnerID(t *testing.T) {
|
||||
id1, err := GenerateRunnerID()
|
||||
if err != nil {
|
||||
t.Fatalf("generate: %v", err)
|
||||
}
|
||||
if len(id1) == 0 {
|
||||
t.Error("expected non-empty ID")
|
||||
}
|
||||
|
||||
id2, _ := GenerateRunnerID()
|
||||
if id1 == id2 {
|
||||
t.Error("expected unique IDs")
|
||||
}
|
||||
|
||||
if id1[:7] != "runner-" {
|
||||
t.Errorf("expected runner- prefix, got %s", id1[:7])
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue