package booking import ( "context" "errors" "log/slog" "testing" "time" "github.com/rjNemo/rentease/internal/config" ) func TestCreateInvoice_StoresInvoicePDFAndPersistsShareURL(t *testing.T) { store := newStubStore() pdfClient := stubInvoicePDF{ file: &GeneratedFile{ Name: "VFNI0240.pdf", ContentType: "application/pdf", Data: []byte("%PDF-1.4 test invoice"), }, } storage := &stubInvoiceStorage{ stored: &StoredInvoiceFile{ ShareURL: "https://minio.local/invoices/1/VFNI0240.pdf?X-Amz-Signature=test", ShareURLExpiresAt: time.Date(2026, time.March, 21, 10, 0, 0, 0, time.UTC), }, } svc, err := NewService(slog.Default(), store, nil, pdfClient, storage, 2*time.Hour) if err != nil { t.Fatalf("unexpected error creating service: %v", err) } booking := NewBooking( time.Date(2026, time.March, 20, 0, 0, 0, 0, time.UTC), time.Date(2026, time.March, 24, 0, 0, 0, 0, time.UTC), "Jane Doe", "+590690441530", "jane@example.com", "Booking", 2, 15, nil, ).WithID(1) doc, err := svc.CreateInvoice(context.Background(), booking, config.NewHost()) if err != nil { t.Fatalf("unexpected error creating invoice: %v", err) } if storage.objectKey != "invoices/1/VFNI0240.pdf" { t.Fatalf("expected invoice object key to include booking folder, got %q", storage.objectKey) } if storage.shareURLTTL != 2*time.Hour { t.Fatalf("expected configured share URL TTL to be forwarded, got %s", storage.shareURLTTL) } if string(storage.file.Data) != "%PDF-1.4 test invoice" { t.Fatalf("expected generated PDF bytes to be uploaded, got %q", string(storage.file.Data)) } if doc.ShareURL != storage.stored.ShareURL { t.Fatalf("expected returned document share URL %q, got %q", storage.stored.ShareURL, doc.ShareURL) } if store.invoiceDocument == nil { t.Fatal("expected invoice document to be persisted") } if store.invoiceDocument.ObjectKey != "invoices/1/VFNI0240.pdf" { t.Fatalf("expected persisted object key, got %q", store.invoiceDocument.ObjectKey) } if store.invoiceDocument.ShareURL != storage.stored.ShareURL { t.Fatalf("expected persisted share URL %q, got %q", storage.stored.ShareURL, store.invoiceDocument.ShareURL) } } func TestCreateInvoice_ReturnsConfigurationErrorWhenStorageIsMissing(t *testing.T) { svc, err := NewService(slog.Default(), newStubStore(), nil, noopPDF{}, nil, time.Hour) if err != nil { t.Fatalf("unexpected error creating service: %v", err) } booking := NewBooking(time.Now(), time.Now().Add(24*time.Hour), "Jane Doe", "", "", "Booking", 1, 0, nil).WithID(1) _, err = svc.CreateInvoice(context.Background(), booking, config.NewHost()) if !errors.Is(err, ErrInvoiceStorageNotConfigured) { t.Fatalf("expected ErrInvoiceStorageNotConfigured, got %v", err) } } type stubInvoicePDF struct { file *GeneratedFile err error } func (s stubInvoicePDF) BuildInvoice(invoice Invoice) (*GeneratedFile, error) { if s.err != nil { return nil, s.err } return s.file, nil } func (stubInvoicePDF) BuildReport(report ReportData, period string, month, year int) (string, error) { return "", nil } type stubInvoiceStorage struct { file GeneratedFile objectKey string shareURLTTL time.Duration stored *StoredInvoiceFile err error } func (s *stubInvoiceStorage) StoreInvoice(ctx context.Context, objectKey string, file GeneratedFile, shareURLTTL time.Duration) (*StoredInvoiceFile, error) { if s.err != nil { return nil, s.err } s.objectKey = objectKey s.file = file s.shareURLTTL = shareURLTTL stored := *s.stored if stored.ObjectKey == "" { stored.ObjectKey = objectKey } return &stored, nil }