177 lines
4.4 KiB
Go
177 lines
4.4 KiB
Go
package documents
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
"github.com/tech/sendico/billing/documents/renderer"
|
|
"github.com/tech/sendico/billing/documents/storage"
|
|
"github.com/tech/sendico/billing/documents/storage/model"
|
|
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type stubRepo struct {
|
|
store storage.DocumentsStore
|
|
}
|
|
|
|
func (s *stubRepo) Ping(ctx context.Context) error { return nil }
|
|
func (s *stubRepo) Documents() storage.DocumentsStore { return s.store }
|
|
|
|
var _ storage.Repository = (*stubRepo)(nil)
|
|
|
|
type stubDocumentsStore struct {
|
|
record *model.DocumentRecord
|
|
updateCalls int
|
|
}
|
|
|
|
func (s *stubDocumentsStore) Create(ctx context.Context, record *model.DocumentRecord) error {
|
|
s.record = record
|
|
return nil
|
|
}
|
|
|
|
func (s *stubDocumentsStore) Update(ctx context.Context, record *model.DocumentRecord) error {
|
|
s.record = record
|
|
s.updateCalls++
|
|
return nil
|
|
}
|
|
|
|
func (s *stubDocumentsStore) GetByPaymentRef(ctx context.Context, paymentRef string) (*model.DocumentRecord, error) {
|
|
return s.record, nil
|
|
}
|
|
|
|
func (s *stubDocumentsStore) ListByPaymentRefs(ctx context.Context, paymentRefs []string) ([]*model.DocumentRecord, error) {
|
|
return []*model.DocumentRecord{s.record}, nil
|
|
}
|
|
|
|
var _ storage.DocumentsStore = (*stubDocumentsStore)(nil)
|
|
|
|
type memDocStore struct {
|
|
data map[string][]byte
|
|
saveCount int
|
|
loadCount int
|
|
}
|
|
|
|
func newMemDocStore() *memDocStore {
|
|
return &memDocStore{data: map[string][]byte{}}
|
|
}
|
|
|
|
func (m *memDocStore) Save(ctx context.Context, key string, data []byte) error {
|
|
m.saveCount++
|
|
copyData := make([]byte, len(data))
|
|
copy(copyData, data)
|
|
m.data[key] = copyData
|
|
return nil
|
|
}
|
|
|
|
func (m *memDocStore) Load(ctx context.Context, key string) ([]byte, error) {
|
|
m.loadCount++
|
|
data := m.data[key]
|
|
copyData := make([]byte, len(data))
|
|
copy(copyData, data)
|
|
return copyData, nil
|
|
}
|
|
|
|
func (m *memDocStore) Counts() (int, int) {
|
|
return m.saveCount, m.loadCount
|
|
}
|
|
|
|
type stubTemplate struct {
|
|
blocks []renderer.Block
|
|
calls int
|
|
}
|
|
|
|
func (s *stubTemplate) Render(snapshot model.ActSnapshot) ([]renderer.Block, error) {
|
|
s.calls++
|
|
return s.blocks, nil
|
|
}
|
|
|
|
func TestGetDocument_IdempotentAndHashed(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
snapshot := model.ActSnapshot{
|
|
PaymentID: "PAY-123",
|
|
Date: time.Date(2026, 1, 30, 0, 0, 0, 0, time.UTC),
|
|
ExecutorFullName: "Jane Doe",
|
|
Amount: decimal.RequireFromString("100.00"),
|
|
Currency: "USD",
|
|
OrgLegalName: "Acme Corp",
|
|
OrgAddress: "42 Galaxy Way",
|
|
}
|
|
|
|
record := &model.DocumentRecord{
|
|
PaymentRef: "PAY-123",
|
|
Snapshot: snapshot,
|
|
Available: []model.DocumentType{model.DocumentTypeAct},
|
|
}
|
|
|
|
documentsStore := &stubDocumentsStore{record: record}
|
|
repo := &stubRepo{store: documentsStore}
|
|
store := newMemDocStore()
|
|
tmpl := &stubTemplate{
|
|
blocks: []renderer.Block{
|
|
{Tag: renderer.TagTitle, Lines: []string{"ACT"}},
|
|
{Tag: renderer.TagText, Lines: []string{"Executor: Jane Doe", "Amount: 100 USD"}},
|
|
},
|
|
}
|
|
|
|
cfg := Config{
|
|
Issuer: renderer.Issuer{
|
|
LegalName: "Sendico Ltd",
|
|
LegalAddress: "12 Market Street, London, UK",
|
|
},
|
|
}
|
|
|
|
svc := NewService(zap.NewNop(), repo, nil,
|
|
WithConfig(cfg),
|
|
WithDocumentStore(store),
|
|
WithTemplateRenderer(tmpl),
|
|
)
|
|
|
|
resp1, err := svc.GetDocument(ctx, &documentsv1.GetDocumentRequest{
|
|
PaymentRef: "PAY-123",
|
|
Type: documentsv1.DocumentType_DOCUMENT_TYPE_ACT,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetDocument first call: %v", err)
|
|
}
|
|
if len(resp1.Content) == 0 {
|
|
t.Fatalf("expected content on first call")
|
|
}
|
|
|
|
hash1 := sha256.Sum256(resp1.Content)
|
|
stored := record.Hashes[model.DocumentTypeAct]
|
|
if stored == "" {
|
|
t.Fatalf("expected stored hash")
|
|
}
|
|
if stored != hex.EncodeToString(hash1[:]) {
|
|
t.Fatalf("stored hash mismatch: got %s", stored)
|
|
}
|
|
|
|
resp2, err := svc.GetDocument(ctx, &documentsv1.GetDocumentRequest{
|
|
PaymentRef: "PAY-123",
|
|
Type: documentsv1.DocumentType_DOCUMENT_TYPE_ACT,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetDocument second call: %v", err)
|
|
}
|
|
if !bytes.Equal(resp1.Content, resp2.Content) {
|
|
t.Fatalf("expected identical PDF bytes on second call")
|
|
}
|
|
|
|
if tmpl.calls != 1 {
|
|
t.Fatalf("expected template to be rendered once, got %d", tmpl.calls)
|
|
}
|
|
if store.saveCount != 1 {
|
|
t.Fatalf("expected document save once, got %d", store.saveCount)
|
|
}
|
|
if store.loadCount == 0 {
|
|
t.Fatalf("expected document load on second call")
|
|
}
|
|
}
|