feat: add test database setup and usage helpers
- Introduced a new test file for setting up an in-memory SQLite database for testing purposes. - Added helper functions for seeding test data, including users, plans, subscriptions, and wallet transactions. - Implemented usage helpers to load user video counts and storage usage. - Created user payload struct and functions to build user payloads with preferences and wallet balance. - Refactored gRPC server setup to include new services and handlers. - Updated proto files to simplify service definitions by removing redundant service prefixes.
This commit is contained in:
195
internal/service/service_admin_jobs_agents_test.go
Normal file
195
internal/service/service_admin_jobs_agents_test.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"gorm.io/gorm"
|
||||
appv1 "stream.api/internal/api/proto/app/v1"
|
||||
"stream.api/internal/database/model"
|
||||
"stream.api/internal/video"
|
||||
runtimeservices "stream.api/internal/video/runtime/services"
|
||||
)
|
||||
|
||||
func TestListAdminJobsCursorPagination(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoService = video.NewService(db, runtimeservices.NewJobService(nil, nil))
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
|
||||
baseTime := time.Date(2026, 3, 22, 10, 0, 0, 0, time.UTC)
|
||||
seedTestJob(t, db, model.Job{ID: "job-300", CreatedAt: ptrTime(baseTime.Add(time.Minute)), UpdatedAt: ptrTime(baseTime.Add(time.Minute))})
|
||||
seedTestJob(t, db, model.Job{ID: "job-200", CreatedAt: ptrTime(baseTime), UpdatedAt: ptrTime(baseTime)})
|
||||
seedTestJob(t, db, model.Job{ID: "job-100", CreatedAt: ptrTime(baseTime), UpdatedAt: ptrTime(baseTime)})
|
||||
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
|
||||
client := newAdminClient(conn)
|
||||
resp, err := client.ListAdminJobs(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.ListAdminJobsRequest{PageSize: 2})
|
||||
if err != nil {
|
||||
t.Fatalf("ListAdminJobs(first page) error = %v", err)
|
||||
}
|
||||
assertAdminJobIDs(t, resp.GetJobs(), []string{"job-300", "job-200"})
|
||||
if !resp.GetHasMore() {
|
||||
t.Fatal("ListAdminJobs(first page) has_more = false, want true")
|
||||
}
|
||||
if resp.GetNextCursor() == "" {
|
||||
t.Fatal("ListAdminJobs(first page) next_cursor is empty")
|
||||
}
|
||||
if resp.GetPageSize() != 2 {
|
||||
t.Fatalf("ListAdminJobs(first page) page_size = %d, want 2", resp.GetPageSize())
|
||||
}
|
||||
|
||||
nextCursor := resp.GetNextCursor()
|
||||
resp, err = client.ListAdminJobs(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.ListAdminJobsRequest{
|
||||
Cursor: ptrString(nextCursor),
|
||||
PageSize: 2,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ListAdminJobs(second page) error = %v", err)
|
||||
}
|
||||
assertAdminJobIDs(t, resp.GetJobs(), []string{"job-100"})
|
||||
if resp.GetHasMore() {
|
||||
t.Fatal("ListAdminJobs(second page) has_more = true, want false")
|
||||
}
|
||||
if resp.GetNextCursor() != "" {
|
||||
t.Fatalf("ListAdminJobs(second page) next_cursor = %q, want empty", resp.GetNextCursor())
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAdminJobsInvalidCursor(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoService = video.NewService(db, runtimeservices.NewJobService(nil, nil))
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
|
||||
client := newAdminClient(conn)
|
||||
_, err := client.ListAdminJobs(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.ListAdminJobsRequest{
|
||||
Cursor: ptrString("not-a-valid-cursor"),
|
||||
PageSize: 1,
|
||||
})
|
||||
assertGRPCCode(t, err, codes.InvalidArgument)
|
||||
}
|
||||
|
||||
func TestListAdminJobsCursorRejectsAgentMismatch(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoService = video.NewService(db, runtimeservices.NewJobService(nil, nil))
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
|
||||
baseTime := time.Date(2026, 3, 22, 11, 0, 0, 0, time.UTC)
|
||||
agentOne := int64(101)
|
||||
agentTwo := int64(202)
|
||||
seedTestJob(t, db, model.Job{ID: "job-b", AgentID: &agentOne, CreatedAt: ptrTime(baseTime.Add(time.Minute)), UpdatedAt: ptrTime(baseTime.Add(time.Minute))})
|
||||
seedTestJob(t, db, model.Job{ID: "job-a", AgentID: &agentOne, CreatedAt: ptrTime(baseTime), UpdatedAt: ptrTime(baseTime)})
|
||||
seedTestJob(t, db, model.Job{ID: "job-x", AgentID: &agentTwo, CreatedAt: ptrTime(baseTime.Add(2 * time.Minute)), UpdatedAt: ptrTime(baseTime.Add(2 * time.Minute))})
|
||||
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
|
||||
client := newAdminClient(conn)
|
||||
resp, err := client.ListAdminJobs(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.ListAdminJobsRequest{
|
||||
AgentId: ptrString("101"),
|
||||
PageSize: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ListAdminJobs(filtered first page) error = %v", err)
|
||||
}
|
||||
if resp.GetNextCursor() == "" {
|
||||
t.Fatal("ListAdminJobs(filtered first page) next_cursor is empty")
|
||||
}
|
||||
|
||||
_, err = client.ListAdminJobs(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.ListAdminJobsRequest{
|
||||
AgentId: ptrString("202"),
|
||||
Cursor: ptrString(resp.GetNextCursor()),
|
||||
PageSize: 1,
|
||||
})
|
||||
assertGRPCCode(t, err, codes.InvalidArgument)
|
||||
}
|
||||
|
||||
func ensureTestJobsTable(t *testing.T, db *gorm.DB) {
|
||||
t.Helper()
|
||||
stmt := `CREATE TABLE jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
status TEXT,
|
||||
priority INTEGER,
|
||||
input_url TEXT,
|
||||
output_url TEXT,
|
||||
total_duration INTEGER,
|
||||
current_time INTEGER,
|
||||
progress REAL,
|
||||
agent_id INTEGER,
|
||||
logs TEXT,
|
||||
config TEXT,
|
||||
cancelled BOOLEAN NOT NULL DEFAULT 0,
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
max_retries INTEGER NOT NULL DEFAULT 3,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
)`
|
||||
if err := db.Exec(stmt).Error; err != nil {
|
||||
t.Fatalf("create jobs table: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func seedTestJob(t *testing.T, db *gorm.DB, job model.Job) model.Job {
|
||||
t.Helper()
|
||||
if job.Status == nil {
|
||||
job.Status = ptrString("pending")
|
||||
}
|
||||
if job.Priority == nil {
|
||||
job.Priority = ptrInt64(0)
|
||||
}
|
||||
if job.Config == nil {
|
||||
job.Config = ptrString(`{"name":"` + job.ID + `"}`)
|
||||
}
|
||||
if job.Cancelled == nil {
|
||||
job.Cancelled = ptrBool(false)
|
||||
}
|
||||
if job.RetryCount == nil {
|
||||
job.RetryCount = ptrInt64(0)
|
||||
}
|
||||
if job.MaxRetries == nil {
|
||||
job.MaxRetries = ptrInt64(3)
|
||||
}
|
||||
if job.CreatedAt == nil {
|
||||
job.CreatedAt = ptrTime(time.Now().UTC())
|
||||
}
|
||||
if job.UpdatedAt == nil {
|
||||
job.UpdatedAt = ptrTime(job.CreatedAt.UTC())
|
||||
}
|
||||
if job.Version == nil {
|
||||
job.Version = ptrInt64(1)
|
||||
}
|
||||
if err := db.Create(&job).Error; err != nil {
|
||||
t.Fatalf("create job %s: %v", job.ID, err)
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func assertAdminJobIDs(t *testing.T, jobs []*appv1.AdminJob, want []string) {
|
||||
t.Helper()
|
||||
if len(jobs) != len(want) {
|
||||
t.Fatalf("job count = %d, want %d", len(jobs), len(want))
|
||||
}
|
||||
for i, job := range jobs {
|
||||
if job.GetId() != want[i] {
|
||||
t.Fatalf("job[%d].id = %q, want %q", i, job.GetId(), want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ptrTime(v time.Time) *time.Time { return &v }
|
||||
Reference in New Issue
Block a user