fix(report): footer advertises log attachment only when actually attached
All checks were successful
release / build-and-release (push) Successful in 35s

buildHTML ran before zipLog, so the email footer always claimed
"log v priloze (<host>-Deploy.log.zip)" even when the log was unreadable
and no attachment was added. Zip the log first and pass the attachment
name into buildHTML; footer now names the zip only when present, otherwise
points solely to the local copy. Covered by TestBuildHTMLFooterMatchesAttachment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
X9 Dev 2026-07-28 18:03:45 +02:00
parent eabf207e3f
commit ef56410139
2 changed files with 37 additions and 10 deletions

View file

@ -56,7 +56,11 @@ func Send(results []StepResult, issueURL, logPath string) error {
now := time.Now().Format("2006-01-02 15:04")
subject := fmt.Sprintf("xetup report %s", hostname)
body := buildHTML(results, hostname, now, issueURL)
// Zip the log first (best-effort; nil attachment => plain email) so the
// body's footer can state accurately whether the log is actually attached.
attachName, attachData := zipLog(logPath, hostname)
body := buildHTML(results, hostname, now, issueURL, attachName)
// Always save local copy so technician has a record even if SMTP fails
_ = os.MkdirAll(filepath.Dir(localReportPath), 0755)
@ -64,9 +68,6 @@ func Send(results []StepResult, issueURL, logPath string) error {
fmt.Fprintf(os.Stderr, "[WARN] Failed to save local report: %v\n", err)
}
// Zip the log for attachment (best-effort; nil attachment => plain email)
attachName, attachData := zipLog(logPath, hostname)
// Retry SMTP up to 3 times with exponential backoff (1s, 5s, 15s)
delays := []time.Duration{0, 1 * time.Second, 5 * time.Second}
var lastErr error
@ -168,7 +169,7 @@ func writeBase64(w *bytes.Buffer, data []byte) {
w.WriteString("\r\n")
}
func buildHTML(results []StepResult, hostname, dateTime, issueURL string) string {
func buildHTML(results []StepResult, hostname, dateTime, issueURL, attachName string) string {
var ok, errs, skipped int
var rows strings.Builder
@ -235,14 +236,25 @@ func buildHTML(results []StepResult, hostname, dateTime, issueURL string) string
%s
</div>
<p style="text-align:center;color:#999;font-size:12px;margin-top:16px">
Odeslano z xetup.exe &mdash; log v priloze (%s-Deploy.log.zip) i lokalne: C:\Windows\Setup\Scripts\Deploy.log
%s
</p>
</body></html>`,
hostname, dateTime,
rows.String(),
summaryColor, summaryText, ok, errs, skipped,
trackerLinks(hostname, issueURL),
hostname)
logFooter(attachName))
}
// logFooter names the zipped log in the footer only when it is actually
// attached; otherwise it points solely to the local copy on the machine.
func logFooter(attachName string) string {
if attachName != "" {
return fmt.Sprintf("Odeslano z xetup.exe &mdash; log v priloze (%s) i lokalne: "+
`C:\Windows\Setup\Scripts\Deploy.log`, attachName)
}
return "Odeslano z xetup.exe &mdash; log lokalne: " +
`C:\Windows\Setup\Scripts\Deploy.log`
}
// trackerLinks renders the tracker links block: a direct link to this run's

View file

@ -33,7 +33,7 @@ func writeLog(t *testing.T, content []byte) string {
// buildHTML must embed the direct issue link and the per-machine tracker filter.
func TestBuildHTMLLinks(t *testing.T) {
issueURL := "https://git.xetup.x9.cz/x9/xetup-runs/issues/42"
html := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", issueURL)
html := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", issueURL, "PC-TEST-Deploy.log.zip")
if !strings.Contains(html, issueURL) {
t.Errorf("html does not link the run issue %q", issueURL)
@ -45,7 +45,7 @@ func TestBuildHTMLLinks(t *testing.T) {
// With no issue (clean run / post failed), only the tracker link is shown.
func TestBuildHTMLNoIssue(t *testing.T) {
html := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", "")
html := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", "", "PC-TEST-Deploy.log.zip")
if strings.Contains(html, "Chyby tohoto behu") {
t.Error("issue-specific line shown when issueURL is empty")
}
@ -54,6 +54,21 @@ func TestBuildHTMLNoIssue(t *testing.T) {
}
}
// The footer must only advertise the attachment when one is actually present.
func TestBuildHTMLFooterMatchesAttachment(t *testing.T) {
with := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", "", "PC-TEST-Deploy.log.zip")
if !strings.Contains(with, "v priloze (PC-TEST-Deploy.log.zip)") {
t.Error("footer should name the attachment when one is present")
}
without := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", "", "")
if strings.Contains(without, "v priloze") {
t.Errorf("footer claims an attachment when none is attached:\n%s", without)
}
if !strings.Contains(without, "log lokalne") {
t.Error("footer should still point to the local log copy")
}
}
// zipLog must produce a valid zip whose Deploy.log entry round-trips.
func TestZipLogRoundTrip(t *testing.T) {
content := []byte("line one\nERROR something failed\nline three\n")
@ -90,7 +105,7 @@ func TestZipLogMissing(t *testing.T) {
func TestBuildMessageMultipart(t *testing.T) {
logContent := []byte("deploy log body\nERROR boom\n")
_, zipData := zipLog(writeLog(t, logContent), "PC-TEST")
html := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", "https://x/issues/1")
html := buildHTML(sampleResults, "PC-TEST", "2026-07-28 10:00", "https://x/issues/1", "PC-TEST-Deploy.log.zip")
raw := buildMessage("xetup report PC-TEST", html, "PC-TEST-Deploy.log.zip", zipData)