package service import ( "context" "strings" "testing" "oci-portal/internal/oci" ) // billingStubClient 只桩账单方法,其余经内嵌 fakeClient 兜底。 type billingStubClient struct { *fakeClient invoices []oci.Invoice lines []oci.InvoiceLine methods []oci.PaymentMethod pdf []byte listYear int paidID string paidEmail string } func (f *billingStubClient) ListInvoices(ctx context.Context, cred oci.Credentials, year int) ([]oci.Invoice, error) { f.listYear = year return f.invoices, nil } func (f *billingStubClient) ListInvoiceLines(ctx context.Context, cred oci.Credentials, internalInvoiceID string) ([]oci.InvoiceLine, error) { return f.lines, nil } func (f *billingStubClient) DownloadInvoicePdf(ctx context.Context, cred oci.Credentials, internalInvoiceID string, maxBytes int64) ([]byte, error) { return f.pdf, nil } func (f *billingStubClient) PayInvoice(ctx context.Context, cred oci.Credentials, internalInvoiceID, email string) error { f.paidID, f.paidEmail = internalInvoiceID, email return nil } func (f *billingStubClient) ListPaymentMethods(ctx context.Context, cred oci.Credentials) ([]oci.PaymentMethod, error) { return f.methods, nil } func TestInvoicesAndPaymentMethods(t *testing.T) { client := &billingStubClient{ fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}, invoices: []oci.Invoice{{ID: "inv1", Number: "100001", Status: "OPEN"}}, lines: []oci.InvoiceLine{{Product: "Compute", Total: 12.5}}, methods: []oci.PaymentMethod{{Method: "CREDIT_CARD", LastDigits: "4242"}}, pdf: []byte("%PDF-1.4 fake"), } svc := newTestService(t, client) cfg := importAliveConfig(t, svc) ctx := context.Background() if list, err := svc.Invoices(ctx, cfg.ID, 0); err != nil || len(list) != 1 || list[0].Number != "100001" { t.Fatalf("Invoices = %v, %v", list, err) } if _, err := svc.Invoices(ctx, cfg.ID, 2026); err != nil || client.listYear != 2026 { t.Fatalf("Invoices(year) 透传 = %d, %v, want 2026", client.listYear, err) } if _, err := svc.Invoices(ctx, cfg.ID, 26); err == nil { t.Fatal("Invoices(26) 应拒绝非法年份") } if lines, err := svc.InvoiceLines(ctx, cfg.ID, "6100"); err != nil || len(lines) != 1 { t.Fatalf("InvoiceLines = %v, %v", lines, err) } if data, err := svc.InvoicePdf(ctx, cfg.ID, "6100"); err != nil || len(data) == 0 { t.Fatalf("InvoicePdf = %d bytes, %v", len(data), err) } if ms, err := svc.PaymentMethods(ctx, cfg.ID); err != nil || len(ms) != 1 || ms[0].LastDigits != "4242" { t.Fatalf("PaymentMethods = %v, %v", ms, err) } } func TestPayInvoiceValidation(t *testing.T) { client := &billingStubClient{fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}} svc := newTestService(t, client) cfg := importAliveConfig(t, svc) ctx := context.Background() tests := []struct { name, internalID, email, wantErr string }{ {"内部 ID 为空", "", "a@b.c", "internal invoice id is empty"}, {"邮箱非法", "6100", "not-an-email", "invalid receipt email"}, {"合法请求", "6100", "billing@example.com", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := svc.PayInvoice(ctx, cfg.ID, tt.internalID, tt.email) if tt.wantErr == "" { if err != nil { t.Fatalf("err = %v, want nil", err) } if client.paidID != tt.internalID || client.paidEmail != tt.email { t.Fatalf("透传 = (%s, %s), want (%s, %s)", client.paidID, client.paidEmail, tt.internalID, tt.email) } return } if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("err = %v, want contains %q", err, tt.wantErr) } }) } }