package services import ( "FamilyHub/src/domain" "context" "errors" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type transactionServiceCreateMock struct { createFn func(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error) getByIDFn func(ctx context.Context, id int64) (*domain.Transaction, error) } func (m *transactionServiceCreateMock) Create(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error) { if m.createFn != nil { return m.createFn(ctx, req) } return nil, errors.New("mock is not configured") } func (m *transactionServiceCreateMock) GetByID(ctx context.Context, id int64) (*domain.Transaction, error) { if m.getByIDFn != nil { return m.getByIDFn(ctx, id) } return nil, errors.New("mock is not configured") } func (m *transactionServiceCreateMock) List(ctx context.Context, filter domain.TransactionListFilter) ([]*domain.Transaction, error) { return nil, errors.New("not implemented") } func (m *transactionServiceCreateMock) Analytics(ctx context.Context, filter domain.TransactionAnalyticsFilter) (domain.TransactionAnalytics, error) { return domain.TransactionAnalytics{}, errors.New("not implemented") } func (m *transactionServiceCreateMock) Update(ctx context.Context, id int64, req domain.UpdateTransactionRequest) (*domain.Transaction, error) { return nil, errors.New("not implemented") } func (m *transactionServiceCreateMock) Delete(ctx context.Context, id int64) error { return errors.New("not implemented") } type receiptServiceCreateMock struct { addReceiptFn func(ctx context.Context, req domain.AddReceiptRequest) (*domain.Receipt, error) } func (m *receiptServiceCreateMock) AddReceipt(ctx context.Context, req domain.AddReceiptRequest) (*domain.Receipt, error) { if m.addReceiptFn != nil { return m.addReceiptFn(ctx, req) } return nil, errors.New("mock is not configured") } type ocrCreateMock struct { recognizeFn func(ctx context.Context, image []byte) (string, error) } func (m *ocrCreateMock) Recognize(ctx context.Context, image []byte) (string, error) { if m.recognizeFn != nil { return m.recognizeFn(ctx, image) } return "", errors.New("mock is not configured") } func (m *ocrCreateMock) Close() error { return nil } func TestTransactionCreationService_Create_Manual(t *testing.T) { now := time.Date(2026, time.January, 21, 10, 11, 12, 0, time.UTC) svc := NewTransactionCreationService( &transactionServiceCreateMock{createFn: func(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error) { assert.Equal(t, int64(1), req.FamilyID) return &domain.Transaction{ID: 1, FamilyID: req.FamilyID, DateTime: now}, nil }}, nil, nil, ) transaction, err := svc.Create(context.Background(), CreateTransactionInput{ Manual: &domain.CreateTransactionRequest{FamilyID: 1}, }) require.NoError(t, err) assert.Equal(t, int64(1), transaction.ID) } func TestTransactionCreationService_Create_Receipt(t *testing.T) { now := time.Date(2026, time.January, 21, 10, 11, 12, 0, time.UTC) svc := NewTransactionCreationService( &transactionServiceCreateMock{getByIDFn: func(ctx context.Context, id int64) (*domain.Transaction, error) { assert.Equal(t, int64(15), id) return &domain.Transaction{ID: id, DateTime: now}, nil }}, &receiptServiceCreateMock{addReceiptFn: func(ctx context.Context, req domain.AddReceiptRequest) (*domain.Receipt, error) { assert.Equal(t, "123", req.Number) return &domain.Receipt{TransactionID: ptrInt64Service(15)}, nil }}, nil, ) transaction, err := svc.Create(context.Background(), CreateTransactionInput{ Receipt: &domain.AddReceiptRequest{Number: "123"}, }) require.NoError(t, err) assert.Equal(t, int64(15), transaction.ID) } func TestTransactionCreationService_Create_Photo(t *testing.T) { now := time.Date(2026, time.January, 21, 10, 11, 12, 0, time.UTC) svc := NewTransactionCreationService( &transactionServiceCreateMock{getByIDFn: func(ctx context.Context, id int64) (*domain.Transaction, error) { assert.Equal(t, int64(17), id) return &domain.Transaction{ID: id, DateTime: now}, nil }}, &receiptServiceCreateMock{addReceiptFn: func(ctx context.Context, req domain.AddReceiptRequest) (*domain.Receipt, error) { assert.Equal(t, strings.Repeat("1", 24), req.Number) assert.Equal(t, "21.01.2026", req.Date) return &domain.Receipt{TransactionID: ptrInt64Service(17)}, nil }}, &ocrCreateMock{recognizeFn: func(ctx context.Context, image []byte) (string, error) { assert.Equal(t, []byte("image"), image) return "21.01.2026 " + strings.Repeat("1", 24), nil }}, ) familyID := int64(1) createdBy := int64(2) transaction, err := svc.Create(context.Background(), CreateTransactionInput{ Photo: &CreateTransactionPhotoInput{ Image: []byte("image"), FamilyID: &familyID, CreatedBy: &createdBy, }, }) require.NoError(t, err) assert.Equal(t, int64(17), transaction.ID) } func TestTransactionCreationService_Create_PhotoRequiresActors(t *testing.T) { svc := NewTransactionCreationService( &transactionServiceCreateMock{}, &receiptServiceCreateMock{}, &ocrCreateMock{}, ) _, err := svc.Create(context.Background(), CreateTransactionInput{ Photo: &CreateTransactionPhotoInput{Image: []byte("image")}, }) require.ErrorIs(t, err, ErrReceiptTransactionActorsMissing) } func ptrInt64Service(v int64) *int64 { return &v }