refactor: DRY auth path, fix Sprintf/RE2 bugs; real integration tests only

Why
---
- Sprintf(*p.Title) fed the title as a format string — % in titles broke
  the String() method; also panicked on nil Title.
- internal/applications.buildSummary used RE2-unsupported `(?= ...)`
  lookahead inside regexp.MustCompile, panicking the first time the
  applications-sync path was exercised on Go 1.23+.
- Query() duplicated the auth-expiry check inline while ensureToken()
  already handled it — two code paths drifted.
- Request.Debug split Query() into two branches that both unmarshalled
  into the same target value. Dead code.
- httptest fixtures that emulated OnlyOffice endpoints were lying to us:
  they passed locally yet never caught a single real protocol regression.

What
----
- Project.String(): nil-safe, no Sprintf format-string interpretation.
- buildSummary regex: RE2-safe non-capturing trailing delimiter
  `(?:\n## |$)` replaces the lookahead.
- Query() routes through ensureToken(); body marshalling factored into
  an unexported requestBodyReader(). Debug flag retained for backwards
  compatibility, documented as a no-op, to be removed at next major.
- Dropped Debug: true stray flags in GetTasks/UpdateProjectTask.
- Deleted httptest-based OnlyOffice mocks. unit_test.go is now pure Go
  (parsers, helpers, env aliases, ctx cancellation against an unroutable
  address). client_test.go is `//go:build integration` and runs against
  a real OnlyOffice, skipping cleanly without ONLYOFFICE_URL/USER/PASS.
- AGENTS.md + .cursor/rules/no-synthetic-mocks.mdc document the new
  testing policy.

Verified
--------
- `go test ./...` green (15 unit tests across package + internal).
- `go test -tags=integration ./...` green against live
  office.produktor.io (5 integration tests: auth, projects, lifecycle,
  calendar+CRM read, task list).
- inventar-sync smoke dry-run against live OO project 33 + Gitea found
  30 tasks, 0 mutations.

Made-with: Cursor
This commit is contained in:
2026-04-24 12:36:40 +01:00
parent 7ff947faa7
commit d5d249424b
9 changed files with 508 additions and 447 deletions
+31
View File
@@ -20,3 +20,34 @@ func TestParseSalary(t *testing.T) {
t.Fatalf("daily: %v", v)
}
}
// TestBuildSummaryFitAssessment covers the RE2-safe replacement of a previously
// lookahead-based regex that would panic at MustCompile time on Go regexp.
func TestBuildSummaryFitAssessment(t *testing.T) {
app := Data{Position: "SRE", Company: "Acme", Folder: "/tmp"}
withNextSection := "## Fit Assessment\nGreat fit overall.\nStrong background.\n## Next section\nUnrelated."
summary := buildSummary(app, withNextSection)
if !contains(summary, "Fit Assessment:") {
t.Fatalf("missing header in summary:\n%s", summary)
}
if !contains(summary, "Great fit overall.") {
t.Fatalf("missing body in summary:\n%s", summary)
}
if contains(summary, "Unrelated.") {
t.Fatalf("bled into next section:\n%s", summary)
}
eof := "## Fit Assessment\nOnly content at EOF."
summary = buildSummary(app, eof)
if !contains(summary, "Only content at EOF.") {
t.Fatalf("EOF case missing body:\n%s", summary)
}
}
func contains(haystack, needle string) bool {
for i := 0; i+len(needle) <= len(haystack); i++ {
if haystack[i:i+len(needle)] == needle {
return true
}
}
return false
}