Files
any2anexoj/internal/writing/html/writer_test.go
T
natercio 5c146ad60f
Generate check / check-changes (pull_request) Successful in 2s
Generate check / verify-generate (pull_request) Skipped
Quality / check-changes (pull_request) Successful in 2s
Quality / static-checks (pull_request) Failing after 13s
Quality / run-tests (pull_request) Failing after 54s
better domain isolation
2026-10-05 15:08:26 +01:00

181 lines
5.4 KiB
Go

package html_test
import (
"bytes"
"strings"
"testing"
"time"
"github.com/nmoniz/any2anexoj/internal"
"github.com/nmoniz/any2anexoj/internal/writing"
htmlwriter "github.com/nmoniz/any2anexoj/internal/writing/html"
"github.com/shopspring/decimal"
)
// buildTestAggregator returns an AggregatorWriter seeded with two
// deterministic ReportItems so every test can assert on a stable document
// shape. Mirrors the fixture pattern used in
// internal/writing/table/printer_test.go.
func buildTestAggregator(t *testing.T) *internal.AggregatorWriter {
t.Helper()
aw := internal.NewAggregatorWriter()
ctx := t.Context()
err := aw.Write(ctx, internal.ReportItem{
Symbol: "AAPL",
Nature: internal.NatureG01,
BrokerCountry: 826,
AssetCountry: 840,
BuyValue: decimal.NewFromFloat(100.50),
BuyTimestamp: time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
SellValue: decimal.NewFromFloat(150.75),
SellTimestamp: time.Date(2023, 6, 20, 0, 0, 0, 0, time.UTC),
Fees: decimal.NewFromFloat(2.50),
Taxes: decimal.NewFromFloat(5.00),
})
if err != nil {
t.Fatalf("failed to write first report item: %v", err)
}
err = aw.Write(ctx, internal.ReportItem{
Symbol: "GOOGL",
Nature: internal.NatureG20,
BrokerCountry: 826,
AssetCountry: 840,
BuyValue: decimal.NewFromFloat(200.00),
BuyTimestamp: time.Date(2023, 3, 10, 0, 0, 0, 0, time.UTC),
SellValue: decimal.NewFromFloat(225.50),
SellTimestamp: time.Date(2023, 9, 5, 0, 0, 0, 0, time.UTC),
Fees: decimal.NewFromFloat(3.00),
Taxes: decimal.NewFromFloat(7.50),
})
if err != nil {
t.Fatalf("failed to write second report item: %v", err)
}
return aw
}
func TestWriter_Render(t *testing.T) {
loc, err := writing.NewLocalizer("en")
if err != nil {
t.Fatalf("create localizer: %v", err)
}
var buf bytes.Buffer
hw := htmlwriter.NewWriter(&buf, loc, "en")
if err := hw.Render(buildTestAggregator(t)); err != nil {
t.Fatalf("Render returned an error: %v", err)
}
got := buf.String()
wantSubstrings := []string{
"<!DOCTYPE html>",
`<html lang="en">`,
`<meta charset="utf-8">`,
`<meta name="viewport" content="width=device-width, initial-scale=1">`,
`<title>Tax report</title>`,
`https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css`,
`integrity="`,
`crossorigin="anonymous"`,
`<main class="container-fluid py-4">`,
`<h1>Tax report</h1>`,
`<div class="table-responsive">`,
`<table class="table table-striped table-hover table-bordered" style="table-layout: fixed">`,
`<colgroup>`,
`</table>`,
`<tfoot>`,
`</tfoot>`,
`</html>`,
`<p class="text-muted small">Generated at `,
}
for _, s := range wantSubstrings {
if !strings.Contains(got, s) {
t.Errorf("rendered HTML missing expected substring %q\nGot:\n%s", s, got)
}
}
// The table fragment must be embedded as raw HTML, not escaped.
if strings.Contains(got, "&lt;table") {
t.Errorf("table fragment appears to be HTML-escaped; expected raw <table> markup\nGot:\n%s", got)
}
if !strings.Contains(got, "<table") {
t.Errorf("expected raw <table> tag in output\nGot:\n%s", got)
}
// Generated timestamp must be valid RFC3339 UTC.
startIdx := strings.Index(got, "Generated at ")
if startIdx == -1 {
t.Fatalf("missing 'Generated at' prefix in output")
}
timestampLine := got[startIdx:]
endIdx := strings.Index(timestampLine, "</p>")
if endIdx == -1 {
t.Fatalf("missing </p> closing tag after 'Generated at' line")
}
timestampStr := strings.TrimPrefix(strings.SplitN(timestampLine, "</p>", 2)[0], "Generated at ")
if _, err := time.Parse(time.RFC3339, timestampStr); err != nil {
t.Errorf("generation timestamp is not valid RFC3339: %q (err: %v)", timestampStr, err)
}
}
// TestWriter_NoRowDuplication verifies that calling Render twice on the
// same writer produces documents with the same number of <tr> rows, just
// like table.Printer's no-duplication guarantee.
func TestWriter_NoRowDuplication(t *testing.T) {
loc, err := writing.NewLocalizer("en")
if err != nil {
t.Fatalf("create localizer: %v", err)
}
var buf bytes.Buffer
hw := htmlwriter.NewWriter(&buf, loc, "en")
aw := buildTestAggregator(t)
if err := hw.Render(aw); err != nil {
t.Fatalf("first Render returned an error: %v", err)
}
first := buf.String()
buf.Reset()
if err := hw.Render(aw); err != nil {
t.Fatalf("second Render returned an error: %v", err)
}
second := buf.String()
firstRows := strings.Count(first, "<tr")
secondRows := strings.Count(second, "<tr")
if firstRows == 0 {
t.Fatalf("expected at least one <tr> row in the first render; got:\n%s", first)
}
if firstRows != secondRows {
t.Errorf("expected second Render to produce the same number of <tr> rows as the first (no duplication)\nfirst rows=%d, second rows=%d\nFirst:\n%s\nSecond:\n%s",
firstRows, secondRows, first, second)
}
}
func TestWriter_LangAttribute(t *testing.T) {
loc, err := writing.NewLocalizer("pt")
if err != nil {
t.Fatalf("create localizer: %v", err)
}
var buf bytes.Buffer
hw := htmlwriter.NewWriter(&buf, loc, "pt")
if err := hw.Render(buildTestAggregator(t)); err != nil {
t.Fatalf("Render returned an error: %v", err)
}
got := buf.String()
if !strings.Contains(got, `<html lang="pt">`) {
t.Errorf("expected <html lang=\"pt\"> attribute; got:\n%s", got)
}
if !strings.Contains(got, `<h1>Relatório fiscal</h1>`) {
t.Errorf("expected Portuguese title in <h1>; got:\n%s", got)
}
}