update cicd
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"gorm.io/gorm"
|
||||
redisadapter "stream.api/internal/adapters/redis"
|
||||
appv1 "stream.api/internal/api/proto/app/v1"
|
||||
"stream.api/internal/database/model"
|
||||
runtimeservices "stream.api/internal/service/runtime/services"
|
||||
renderworkflow "stream.api/internal/workflow/render"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,7 @@ func TestListAdminJobsCursorPagination(t *testing.T) {
|
||||
ensureTestJobsTable(t, db)
|
||||
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, runtimeservices.NewJobService(db, nil, nil))
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, nil, 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)
|
||||
@@ -67,7 +69,7 @@ func TestListAdminJobsInvalidCursor(t *testing.T) {
|
||||
ensureTestJobsTable(t, db)
|
||||
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, runtimeservices.NewJobService(db, nil, nil))
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, nil, nil, nil))
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
@@ -86,7 +88,7 @@ func TestListAdminJobsCursorRejectsAgentMismatch(t *testing.T) {
|
||||
ensureTestJobsTable(t, db)
|
||||
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, runtimeservices.NewJobService(db, nil, nil))
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, nil, 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)
|
||||
@@ -192,4 +194,212 @@ func assertAdminJobIDs(t *testing.T, jobs []*appv1.AdminJob, want []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func ptrTime(v time.Time) *time.Time { return &v }
|
||||
type fakeDLQ struct {
|
||||
entries map[string]*redisadapter.DLQEntry
|
||||
order []string
|
||||
listErr error
|
||||
getErr error
|
||||
removeErr error
|
||||
retryErr error
|
||||
}
|
||||
|
||||
func newFakeDLQ(entries ...*redisadapter.DLQEntry) *fakeDLQ {
|
||||
f := &fakeDLQ{entries: map[string]*redisadapter.DLQEntry{}, order: []string{}}
|
||||
for _, entry := range entries {
|
||||
if entry == nil || entry.Job == nil {
|
||||
continue
|
||||
}
|
||||
f.entries[entry.Job.ID] = entry
|
||||
f.order = append(f.order, entry.Job.ID)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *fakeDLQ) Add(_ context.Context, job *model.Job, reason string) error {
|
||||
if f.entries == nil {
|
||||
f.entries = map[string]*redisadapter.DLQEntry{}
|
||||
}
|
||||
entry := &redisadapter.DLQEntry{Job: job, FailureTime: time.Now().UTC(), Reason: reason}
|
||||
f.entries[job.ID] = entry
|
||||
f.order = append(f.order, job.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeDLQ) Get(_ context.Context, jobID string) (*redisadapter.DLQEntry, error) {
|
||||
if f.getErr != nil {
|
||||
return nil, f.getErr
|
||||
}
|
||||
entry, ok := f.entries[jobID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("job not found in DLQ")
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (f *fakeDLQ) List(_ context.Context, offset, limit int64) ([]*redisadapter.DLQEntry, error) {
|
||||
if f.listErr != nil {
|
||||
return nil, f.listErr
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = int64(len(f.order))
|
||||
}
|
||||
items := make([]*redisadapter.DLQEntry, 0)
|
||||
for i := offset; i < int64(len(f.order)) && int64(len(items)) < limit; i++ {
|
||||
id := f.order[i]
|
||||
if entry, ok := f.entries[id]; ok {
|
||||
items = append(items, entry)
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (f *fakeDLQ) Count(_ context.Context) (int64, error) {
|
||||
return int64(len(f.entries)), nil
|
||||
}
|
||||
|
||||
func (f *fakeDLQ) Remove(_ context.Context, jobID string) error {
|
||||
if f.removeErr != nil {
|
||||
return f.removeErr
|
||||
}
|
||||
if _, ok := f.entries[jobID]; !ok {
|
||||
return fmt.Errorf("job not found in DLQ")
|
||||
}
|
||||
delete(f.entries, jobID)
|
||||
filtered := make([]string, 0, len(f.order))
|
||||
for _, id := range f.order {
|
||||
if id != jobID {
|
||||
filtered = append(filtered, id)
|
||||
}
|
||||
}
|
||||
f.order = filtered
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeDLQ) Retry(ctx context.Context, jobID string) (*model.Job, error) {
|
||||
if f.retryErr != nil {
|
||||
return nil, f.retryErr
|
||||
}
|
||||
entry, err := f.Get(ctx, jobID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := f.Remove(ctx, jobID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return entry.Job, nil
|
||||
}
|
||||
|
||||
type fakeQueue struct {
|
||||
enqueueErr error
|
||||
}
|
||||
|
||||
func (f *fakeQueue) Enqueue(_ context.Context, _ *model.Job) error { return f.enqueueErr }
|
||||
func (f *fakeQueue) Dequeue(_ context.Context) (*model.Job, error) { return nil, nil }
|
||||
|
||||
func TestAdminDlqJobs(t *testing.T) {
|
||||
t.Run("list happy path", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
job1 := seedTestJob(t, db, model.Job{ID: "job-dlq-1", CreatedAt: ptrTime(time.Now().Add(-2 * time.Hour).UTC()), UpdatedAt: ptrTime(time.Now().Add(-2 * time.Hour).UTC())})
|
||||
job2 := seedTestJob(t, db, model.Job{ID: "job-dlq-2", CreatedAt: ptrTime(time.Now().Add(-time.Hour).UTC()), UpdatedAt: ptrTime(time.Now().Add(-time.Hour).UTC())})
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, &fakeQueue{}, nil, newFakeDLQ(
|
||||
&redisadapter.DLQEntry{Job: &job1, FailureTime: time.Now().Add(-30 * time.Minute).UTC(), Reason: "lease_expired", RetryCount: 2},
|
||||
&redisadapter.DLQEntry{Job: &job2, FailureTime: time.Now().Add(-10 * time.Minute).UTC(), Reason: "invalid_config", RetryCount: 3},
|
||||
)))
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newAdminClient(conn)
|
||||
resp, err := client.ListAdminDlqJobs(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.ListAdminDlqJobsRequest{Offset: 0, Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("ListAdminDlqJobs error = %v", err)
|
||||
}
|
||||
if resp.GetTotal() != 2 {
|
||||
t.Fatalf("total = %d, want 2", resp.GetTotal())
|
||||
}
|
||||
if len(resp.GetItems()) != 2 {
|
||||
t.Fatalf("items len = %d, want 2", len(resp.GetItems()))
|
||||
}
|
||||
if resp.GetItems()[0].GetJob().GetId() != "job-dlq-1" {
|
||||
t.Fatalf("first job id = %q", resp.GetItems()[0].GetJob().GetId())
|
||||
}
|
||||
if resp.GetItems()[0].GetReason() != "lease_expired" {
|
||||
t.Fatalf("first reason = %q", resp.GetItems()[0].GetReason())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get not found", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, &fakeQueue{}, nil, newFakeDLQ()))
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newAdminClient(conn)
|
||||
_, err := client.GetAdminDlqJob(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.GetAdminDlqJobRequest{Id: "missing"})
|
||||
assertGRPCCode(t, err, codes.NotFound)
|
||||
})
|
||||
|
||||
t.Run("retry happy path", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
job := seedTestJob(t, db, model.Job{ID: "job-dlq-retry", Status: ptrString("failure"), CreatedAt: ptrTime(time.Now().Add(-time.Hour).UTC()), UpdatedAt: ptrTime(time.Now().Add(-time.Hour).UTC())})
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, &fakeQueue{}, nil, newFakeDLQ(
|
||||
&redisadapter.DLQEntry{Job: &job, FailureTime: time.Now().UTC(), Reason: "lease_expired", RetryCount: 1},
|
||||
)))
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newAdminClient(conn)
|
||||
resp, err := client.RetryAdminDlqJob(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.RetryAdminDlqJobRequest{Id: job.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("RetryAdminDlqJob error = %v", err)
|
||||
}
|
||||
if resp.GetJob().GetId() != job.ID {
|
||||
t.Fatalf("job id = %q, want %q", resp.GetJob().GetId(), job.ID)
|
||||
}
|
||||
if resp.GetJob().GetStatus() != "pending" {
|
||||
t.Fatalf("job status = %q, want pending", resp.GetJob().GetStatus())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("remove happy path", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
job := seedTestJob(t, db, model.Job{ID: "job-dlq-remove", CreatedAt: ptrTime(time.Now().Add(-time.Hour).UTC()), UpdatedAt: ptrTime(time.Now().Add(-time.Hour).UTC())})
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, &fakeQueue{}, nil, newFakeDLQ(
|
||||
&redisadapter.DLQEntry{Job: &job, FailureTime: time.Now().UTC(), Reason: "invalid_config", RetryCount: 3},
|
||||
)))
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newAdminClient(conn)
|
||||
resp, err := client.RemoveAdminDlqJob(testActorOutgoingContext(admin.ID, "ADMIN"), &appv1.RemoveAdminDlqJobRequest{Id: job.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("RemoveAdminDlqJob error = %v", err)
|
||||
}
|
||||
if resp.GetStatus() != "removed" {
|
||||
t.Fatalf("status = %q, want removed", resp.GetStatus())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("list permission denied", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ensureTestJobsTable(t, db)
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "user@example.com", Role: ptrString("USER")})
|
||||
services := newTestAppServices(t, db)
|
||||
services.videoWorkflowService = renderworkflow.New(db, NewJobService(db, &fakeQueue{}, nil, newFakeDLQ()))
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newAdminClient(conn)
|
||||
_, err := client.ListAdminDlqJobs(testActorOutgoingContext(user.ID, "USER"), &appv1.ListAdminDlqJobsRequest{})
|
||||
assertGRPCCode(t, err, codes.PermissionDenied)
|
||||
})
|
||||
}
|
||||
|
||||
245
internal/service/__test__/service_video_metadata_test.go
Normal file
245
internal/service/__test__/service_video_metadata_test.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
appv1 "stream.api/internal/api/proto/app/v1"
|
||||
"stream.api/internal/database/model"
|
||||
"stream.api/internal/middleware"
|
||||
)
|
||||
|
||||
func testInternalMetadataContext() context.Context {
|
||||
return metadata.NewOutgoingContext(context.Background(), metadata.Pairs(
|
||||
middleware.ActorMarkerMetadataKey, testTrustedMarker,
|
||||
))
|
||||
}
|
||||
|
||||
func testInvalidInternalMetadataContext() context.Context {
|
||||
return metadata.NewOutgoingContext(context.Background(), metadata.Pairs(
|
||||
middleware.ActorMarkerMetadataKey, "wrong-marker",
|
||||
))
|
||||
}
|
||||
|
||||
func TestGetVideoMetadata(t *testing.T) {
|
||||
t.Run("happy path with owner defaults", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "paid@example.com", Role: ptrString("USER"), PlanID: ptrString("plan-paid")})
|
||||
now := time.Now().UTC()
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now, AdID: nil}
|
||||
if err := db.Create(&video).Error; err != nil {
|
||||
t.Fatalf("create video: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.Domain{ID: uuid.NewString(), UserID: user.ID, Name: "video.example.com"}).Error; err != nil {
|
||||
t.Fatalf("create domain: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.AdTemplate{ID: uuid.NewString(), UserID: user.ID, Name: "default-ad", VastTagURL: "https://ads.example.com/vast", IsDefault: true, IsActive: ptrBool(true)}).Error; err != nil {
|
||||
t.Fatalf("create ad template: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.PopupAd{ID: uuid.NewString(), UserID: user.ID, Type: "banner", Label: "promo", Value: "https://ads.example.com/banner", IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error; err != nil {
|
||||
t.Fatalf("create popup ad: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.PlayerConfig{ID: uuid.NewString(), UserID: user.ID, Name: "default-player", IsDefault: true, IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error; err != nil {
|
||||
t.Fatalf("create player config: %v", err)
|
||||
}
|
||||
services := newTestAppServices(t, db)
|
||||
_ = admin
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
resp, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("GetVideoMetadata error = %v", err)
|
||||
}
|
||||
if resp.GetVideo().GetId() != video.ID {
|
||||
t.Fatalf("video id = %q, want %q", resp.GetVideo().GetId(), video.ID)
|
||||
}
|
||||
if resp.GetAdTemplate().GetName() != "default-ad" {
|
||||
t.Fatalf("ad template name = %q", resp.GetAdTemplate().GetName())
|
||||
}
|
||||
if resp.GetActivePopupAd().GetLabel() != "promo" {
|
||||
t.Fatalf("popup label = %q", resp.GetActivePopupAd().GetLabel())
|
||||
}
|
||||
if resp.GetDefaultPlayerConfig().GetName() != "default-player" {
|
||||
t.Fatalf("player config name = %q", resp.GetDefaultPlayerConfig().GetName())
|
||||
}
|
||||
if len(resp.GetDomains()) != 1 {
|
||||
t.Fatalf("domains len = %d, want 1", len(resp.GetDomains()))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing ad template returns failed precondition", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "paid@example.com", Role: ptrString("USER"), PlanID: ptrString("plan-paid")})
|
||||
now := time.Now().UTC()
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now}
|
||||
_ = db.Create(&video).Error
|
||||
_ = db.Create(&model.Domain{ID: uuid.NewString(), UserID: user.ID, Name: "video.example.com"}).Error
|
||||
_ = db.Create(&model.PopupAd{ID: uuid.NewString(), UserID: user.ID, Type: "banner", Label: "promo", Value: "https://ads.example.com/banner", IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error
|
||||
_ = db.Create(&model.PlayerConfig{ID: uuid.NewString(), UserID: user.ID, Name: "default-player", IsDefault: true, IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
_, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
assertGRPCCode(t, err, codes.FailedPrecondition)
|
||||
})
|
||||
|
||||
t.Run("missing popup ad returns failed precondition", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "paid@example.com", Role: ptrString("USER"), PlanID: ptrString("plan-paid")})
|
||||
now := time.Now().UTC()
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now}
|
||||
_ = db.Create(&video).Error
|
||||
_ = db.Create(&model.Domain{ID: uuid.NewString(), UserID: user.ID, Name: "video.example.com"}).Error
|
||||
_ = db.Create(&model.AdTemplate{ID: uuid.NewString(), UserID: user.ID, Name: "default-ad", VastTagURL: "https://ads.example.com/vast", IsDefault: true, IsActive: ptrBool(true)}).Error
|
||||
_ = db.Create(&model.PlayerConfig{ID: uuid.NewString(), UserID: user.ID, Name: "default-player", IsDefault: true, IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
_, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
assertGRPCCode(t, err, codes.FailedPrecondition)
|
||||
})
|
||||
|
||||
t.Run("missing player config returns failed precondition", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "paid@example.com", Role: ptrString("USER"), PlanID: ptrString("plan-paid")})
|
||||
now := time.Now().UTC()
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now}
|
||||
_ = db.Create(&video).Error
|
||||
_ = db.Create(&model.Domain{ID: uuid.NewString(), UserID: user.ID, Name: "video.example.com"}).Error
|
||||
_ = db.Create(&model.AdTemplate{ID: uuid.NewString(), UserID: user.ID, Name: "default-ad", VastTagURL: "https://ads.example.com/vast", IsDefault: true, IsActive: ptrBool(true)}).Error
|
||||
_ = db.Create(&model.PopupAd{ID: uuid.NewString(), UserID: user.ID, Type: "banner", Label: "promo", Value: "https://ads.example.com/banner", IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
_, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
assertGRPCCode(t, err, codes.FailedPrecondition)
|
||||
})
|
||||
|
||||
t.Run("missing domains returns failed precondition", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "paid@example.com", Role: ptrString("USER"), PlanID: ptrString("plan-paid")})
|
||||
now := time.Now().UTC()
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now}
|
||||
_ = db.Create(&video).Error
|
||||
_ = db.Create(&model.AdTemplate{ID: uuid.NewString(), UserID: user.ID, Name: "default-ad", VastTagURL: "https://ads.example.com/vast", IsDefault: true, IsActive: ptrBool(true)}).Error
|
||||
_ = db.Create(&model.PopupAd{ID: uuid.NewString(), UserID: user.ID, Type: "banner", Label: "promo", Value: "https://ads.example.com/banner", IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error
|
||||
_ = db.Create(&model.PlayerConfig{ID: uuid.NewString(), UserID: user.ID, Name: "default-player", IsDefault: true, IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
_, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
assertGRPCCode(t, err, codes.FailedPrecondition)
|
||||
})
|
||||
|
||||
t.Run("free user falls back to system admin config", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "free@example.com", Role: ptrString("USER")})
|
||||
now := time.Now().UTC()
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now}
|
||||
if err := db.Create(&video).Error; err != nil {
|
||||
t.Fatalf("create video: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.Domain{ID: uuid.NewString(), UserID: user.ID, Name: "free.example.com"}).Error; err != nil {
|
||||
t.Fatalf("create domain: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.AdTemplate{ID: uuid.NewString(), UserID: admin.ID, Name: "system-ad", VastTagURL: "https://ads.example.com/system", IsDefault: true, IsActive: ptrBool(true)}).Error; err != nil {
|
||||
t.Fatalf("create system ad template: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.PopupAd{ID: uuid.NewString(), UserID: admin.ID, Type: "banner", Label: "system-popup", Value: "https://ads.example.com/system-popup", IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error; err != nil {
|
||||
t.Fatalf("create system popup ad: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.PlayerConfig{ID: uuid.NewString(), UserID: admin.ID, Name: "system-player", IsDefault: true, IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error; err != nil {
|
||||
t.Fatalf("create system player config: %v", err)
|
||||
}
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
resp, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("GetVideoMetadata error = %v", err)
|
||||
}
|
||||
if resp.GetAdTemplate().GetName() != "system-ad" {
|
||||
t.Fatalf("ad template = %q, want system-ad", resp.GetAdTemplate().GetName())
|
||||
}
|
||||
if resp.GetActivePopupAd().GetLabel() != "system-popup" {
|
||||
t.Fatalf("popup label = %q, want system-popup", resp.GetActivePopupAd().GetLabel())
|
||||
}
|
||||
if resp.GetDefaultPlayerConfig().GetName() != "system-player" {
|
||||
t.Fatalf("player config = %q, want system-player", resp.GetDefaultPlayerConfig().GetName())
|
||||
}
|
||||
if len(resp.GetDomains()) != 1 || resp.GetDomains()[0].GetName() != "free.example.com" {
|
||||
t.Fatalf("domains = %#v, want owner domains", resp.GetDomains())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("video ad id takes precedence over fallback ad template", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "admin@example.com", Role: ptrString("ADMIN")})
|
||||
user := seedTestUser(t, db, model.User{ID: uuid.NewString(), Email: "free@example.com", Role: ptrString("USER")})
|
||||
now := time.Now().UTC()
|
||||
videoAd := model.AdTemplate{ID: uuid.NewString(), UserID: user.ID, Name: "video-ad", VastTagURL: "https://ads.example.com/video", IsDefault: false, IsActive: ptrBool(true)}
|
||||
if err := db.Create(&videoAd).Error; err != nil {
|
||||
t.Fatalf("create video ad template: %v", err)
|
||||
}
|
||||
video := model.Video{ID: uuid.NewString(), UserID: user.ID, Title: "video", URL: "https://cdn.example.com/video.mp4", Status: ptrString("ready"), CreatedAt: &now, UpdatedAt: now, AdID: &videoAd.ID}
|
||||
if err := db.Create(&video).Error; err != nil {
|
||||
t.Fatalf("create video: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.Domain{ID: uuid.NewString(), UserID: user.ID, Name: "free.example.com"}).Error; err != nil {
|
||||
t.Fatalf("create domain: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.AdTemplate{ID: uuid.NewString(), UserID: admin.ID, Name: "system-ad", VastTagURL: "https://ads.example.com/system", IsDefault: true, IsActive: ptrBool(true)}).Error; err != nil {
|
||||
t.Fatalf("create system ad template: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.PopupAd{ID: uuid.NewString(), UserID: admin.ID, Type: "banner", Label: "system-popup", Value: "https://ads.example.com/system-popup", IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error; err != nil {
|
||||
t.Fatalf("create system popup ad: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.PlayerConfig{ID: uuid.NewString(), UserID: admin.ID, Name: "system-player", IsDefault: true, IsActive: ptrBool(true), CreatedAt: &now, UpdatedAt: now}).Error; err != nil {
|
||||
t.Fatalf("create system player config: %v", err)
|
||||
}
|
||||
services := newTestAppServices(t, db)
|
||||
_ = admin
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
resp, err := client.GetVideoMetadata(testInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: video.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("GetVideoMetadata error = %v", err)
|
||||
}
|
||||
if resp.GetAdTemplate().GetName() != "video-ad" {
|
||||
t.Fatalf("ad template = %q, want video-ad", resp.GetAdTemplate().GetName())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid marker returns unauthenticated", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
_, err := client.GetVideoMetadata(testInvalidInternalMetadataContext(), &appv1.GetVideoMetadataRequest{VideoId: uuid.NewString()})
|
||||
assertGRPCCode(t, err, codes.Unauthenticated)
|
||||
})
|
||||
|
||||
t.Run("missing marker returns unauthenticated", func(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
services := newTestAppServices(t, db)
|
||||
conn, cleanup := newTestGRPCServer(t, services)
|
||||
defer cleanup()
|
||||
client := newVideoMetadataClient(conn)
|
||||
_, err := client.GetVideoMetadata(context.Background(), &appv1.GetVideoMetadataRequest{VideoId: uuid.NewString()})
|
||||
assertGRPCCode(t, err, codes.Unauthenticated)
|
||||
})
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
appv1 "stream.api/internal/api/proto/app/v1"
|
||||
"stream.api/internal/database/model"
|
||||
"stream.api/internal/database/query"
|
||||
@@ -210,14 +210,11 @@ func newTestDB(t *testing.T) *gorm.DB {
|
||||
`CREATE TABLE popup_ads (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
image_url TEXT NOT NULL,
|
||||
target_url TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
label TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
start_at DATETIME,
|
||||
end_at DATETIME,
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
close_cooldown_minutes INTEGER NOT NULL DEFAULT 60,
|
||||
max_triggers_per_session INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
@@ -273,6 +270,7 @@ func newTestGRPCServer(t *testing.T, services *appServices) (*grpc.ClientConn, f
|
||||
PlansServer: services,
|
||||
PaymentsServer: services,
|
||||
VideosServer: services,
|
||||
VideoMetadataServer: services,
|
||||
AdminServer: services,
|
||||
})
|
||||
|
||||
@@ -407,6 +405,10 @@ func newAdminClient(conn *grpc.ClientConn) appv1.AdminClient {
|
||||
return appv1.NewAdminClient(conn)
|
||||
}
|
||||
|
||||
func newVideoMetadataClient(conn *grpc.ClientConn) appv1.VideoMetadataClient {
|
||||
return appv1.NewVideoMetadataClient(conn)
|
||||
}
|
||||
|
||||
func ptrTime(v time.Time) *time.Time { return &v }
|
||||
|
||||
func seedTestPopupAd(t *testing.T, db *gorm.DB, item model.PopupAd) model.PopupAd {
|
||||
|
||||
Reference in New Issue
Block a user