veza/veza-backend-api/internal/services/job_service_test.go
senke 7846bbab28 fix(backend): remediation plan — tests, playback_analytics, job queue, gamification
Phase 1 - Backend tests:
- Add PlaybackAnalytics to AutoMigrate in setupTestTrackHandler
- Create migration 081_create_playback_analytics.sql for production
- PlaybackAnalyticsService: return ErrTrackNotFound for missing track
- RecordPlay handler: return 404 when track not found
- CreateShare: use RespondSuccess, fix services.ErrTrackNotFound/ErrForbidden
- GetTrackLikes, UnlikeTrack: use RespondSuccess for consistent response
- GetUserLikedTracks test: fix route /users/:id/likes and params
- GetSharedTrack_InvalidToken: set share service in test

Phase 4 - Job queue transcoding:
- Add EnqueueTranscodingJob to JobEnqueuer interface
- Add TypeTranscoding and processTranscodingJob (stub) in JobWorker
- MockJobEnqueuer: implement EnqueueTranscodingJob

Phase 5 - Gamification cleanup:
- Move api_manager.go to internal/api/archive/
- Add archive/README.md documenting archived modules
- Update TODOS_AUDIT.md and FEATURE_STATUS.md
2026-02-17 16:01:45 +01:00

260 lines
6.6 KiB
Go

package services
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.uber.org/zap"
)
// MockJobEnqueuer is a mock implementation of JobEnqueuer interface
type MockJobEnqueuer struct {
mock.Mock
}
func (m *MockJobEnqueuer) EnqueueEmailJob(to, subject, body string) {
m.Called(to, subject, body)
}
func (m *MockJobEnqueuer) EnqueueEmailJobWithTemplate(to, subject, templateName string, templateData map[string]interface{}) {
m.Called(to, subject, templateName, templateData)
}
func (m *MockJobEnqueuer) EnqueueThumbnailJob(inputPath, outputPath string, width, height int) {
m.Called(inputPath, outputPath, width, height)
}
func (m *MockJobEnqueuer) EnqueueTranscodingJob(trackID uuid.UUID, inputPath, outputDir string) {}
func (m *MockJobEnqueuer) EnqueueAnalyticsJob(eventName string, userID *uuid.UUID, payload map[string]interface{}) {
m.Called(eventName, userID, payload)
}
func setupTestJobService(t *testing.T) (*JobService, *MockJobEnqueuer) {
logger := zap.NewNop()
service := NewJobService(logger)
mockEnqueuer := new(MockJobEnqueuer)
service.SetJobEnqueuer(mockEnqueuer)
return service, mockEnqueuer
}
func TestJobService_NewJobService(t *testing.T) {
logger := zap.NewNop()
service := NewJobService(logger)
assert.NotNil(t, service)
assert.NotNil(t, service.logger)
assert.Nil(t, service.jobEnqueuer) // Should be nil initially
}
func TestJobService_SetJobEnqueuer(t *testing.T) {
logger := zap.NewNop()
service := NewJobService(logger)
mockEnqueuer := new(MockJobEnqueuer)
service.SetJobEnqueuer(mockEnqueuer)
assert.Equal(t, mockEnqueuer, service.jobEnqueuer)
}
func TestJobService_EnqueueEmail_Success(t *testing.T) {
service, mockEnqueuer := setupTestJobService(t)
ctx := context.Background()
payload := &EmailPayload{
To: "test@example.com",
Subject: "Test Subject",
Body: "Test Body",
}
mockEnqueuer.On("EnqueueEmailJob", payload.To, payload.Subject, payload.Body).Return()
err := service.EnqueueEmail(ctx, payload)
assert.NoError(t, err)
mockEnqueuer.AssertExpectations(t)
}
func TestJobService_EnqueueEmail_NoEnqueuer(t *testing.T) {
logger := zap.NewNop()
service := NewJobService(logger)
// Don't set enqueuer
ctx := context.Background()
payload := &EmailPayload{
To: "test@example.com",
Subject: "Test Subject",
Body: "Test Body",
}
// Should not error, just log warning
err := service.EnqueueEmail(ctx, payload)
assert.NoError(t, err)
}
func TestJobService_EnqueueEmail_EmptyPayload(t *testing.T) {
service, mockEnqueuer := setupTestJobService(t)
ctx := context.Background()
payload := &EmailPayload{
To: "",
Subject: "",
Body: "",
}
mockEnqueuer.On("EnqueueEmailJob", "", "", "").Return()
err := service.EnqueueEmail(ctx, payload)
assert.NoError(t, err)
mockEnqueuer.AssertExpectations(t)
}
func TestJobService_EnqueueThumbnail_Success(t *testing.T) {
service, mockEnqueuer := setupTestJobService(t)
ctx := context.Background()
payload := &ThumbnailPayload{
TrackID: 123,
FileID: "file123",
FilePath: "/path/to/file.jpg",
}
mockEnqueuer.On("EnqueueThumbnailJob", "/path/to/file.jpg", "/path/to/file.jpg.thumb", 300, 300).Return()
err := service.EnqueueThumbnail(ctx, payload)
assert.NoError(t, err)
mockEnqueuer.AssertExpectations(t)
}
func TestJobService_EnqueueThumbnail_NoEnqueuer(t *testing.T) {
logger := zap.NewNop()
service := NewJobService(logger)
// Don't set enqueuer
ctx := context.Background()
payload := &ThumbnailPayload{
TrackID: 123,
FileID: "file123",
FilePath: "/path/to/file.jpg",
}
// Should not error, just log warning
err := service.EnqueueThumbnail(ctx, payload)
assert.NoError(t, err)
}
func TestJobService_EnqueueThumbnail_EmptyFilePath(t *testing.T) {
service, mockEnqueuer := setupTestJobService(t)
ctx := context.Background()
payload := &ThumbnailPayload{
TrackID: 123,
FileID: "file123",
FilePath: "", // Empty file path
}
// Should not call enqueuer if FilePath is empty
err := service.EnqueueThumbnail(ctx, payload)
assert.NoError(t, err)
mockEnqueuer.AssertNotCalled(t, "EnqueueThumbnailJob")
}
func TestJobService_JobTypes_Constants(t *testing.T) {
assert.Equal(t, "email:send", TypeEmailSend)
assert.Equal(t, "thumbnail:generate", TypeThumbnailGenerate)
assert.Equal(t, "analytics:process", TypeAnalyticsProcess)
assert.Equal(t, "webhook:delivery", TypeWebhookDelivery)
}
func TestJobService_EmailPayload_Fields(t *testing.T) {
payload := &EmailPayload{
To: "test@example.com",
Subject: "Test Subject",
Body: "Test Body",
}
assert.Equal(t, "test@example.com", payload.To)
assert.Equal(t, "Test Subject", payload.Subject)
assert.Equal(t, "Test Body", payload.Body)
}
func TestJobService_ThumbnailPayload_Fields(t *testing.T) {
payload := &ThumbnailPayload{
TrackID: 123,
FileID: "file123",
FilePath: "/path/to/file.jpg",
}
assert.Equal(t, uint(123), payload.TrackID)
assert.Equal(t, "file123", payload.FileID)
assert.Equal(t, "/path/to/file.jpg", payload.FilePath)
}
func TestJobService_EnqueueEmail_MultipleCalls(t *testing.T) {
service, mockEnqueuer := setupTestJobService(t)
ctx := context.Background()
payload1 := &EmailPayload{
To: "user1@example.com",
Subject: "Subject 1",
Body: "Body 1",
}
payload2 := &EmailPayload{
To: "user2@example.com",
Subject: "Subject 2",
Body: "Body 2",
}
mockEnqueuer.On("EnqueueEmailJob", payload1.To, payload1.Subject, payload1.Body).Return()
mockEnqueuer.On("EnqueueEmailJob", payload2.To, payload2.Subject, payload2.Body).Return()
err1 := service.EnqueueEmail(ctx, payload1)
err2 := service.EnqueueEmail(ctx, payload2)
assert.NoError(t, err1)
assert.NoError(t, err2)
mockEnqueuer.AssertExpectations(t)
}
func TestJobService_EnqueueThumbnail_DifferentPaths(t *testing.T) {
service, mockEnqueuer := setupTestJobService(t)
ctx := context.Background()
testCases := []struct {
name string
filePath string
expectedOutput string
}{
{"JPG", "/path/to/image.jpg", "/path/to/image.jpg.thumb"},
{"PNG", "/path/to/image.png", "/path/to/image.png.thumb"},
{"Nested path", "/very/deep/path/to/image.jpg", "/very/deep/path/to/image.jpg.thumb"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
payload := &ThumbnailPayload{
TrackID: 123,
FileID: "file123",
FilePath: tc.filePath,
}
mockEnqueuer.On("EnqueueThumbnailJob", tc.filePath, tc.expectedOutput, 300, 300).Return()
err := service.EnqueueThumbnail(ctx, payload)
assert.NoError(t, err)
})
}
mockEnqueuer.AssertExpectations(t)
}