gitea package (new): minimal client mirroring caldav's structure
- client.go: token auth, 5s timeout, ErrNotFound
- issues.go: ListIssues(owner, repo, opts) hitting
/repos/{o}/{r}/issues?type=issues&state=…&since=…, ParseRepoRef,
RepoHTMLURL. PullRequest-flagged rows dropped server- and client-side.
- httptest stubs covering parse, 404, ParseRepoRef variants.
web wiring:
- Server.Gitea optional GiteaDeps (Client + in-memory 3-min TTL cache
keyed by owner/repo|state).
- detailIssues iterates every gitea-repo link, sums open issues, captures
last-30d closed (≤20) into a disclosure. Per-repo failures surface as
banner; one missing repo never blanks the section.
- relativeTime renders "Nm/h/d ago" / "yesterday" / fallback date.
Templates:
- issues_section.tmpl: per-repo block, header "Issues (n) + ↗ Gitea repo",
rows with #N · title · labels · milestone · assignees · updated.
Titles open in new tab.
- detail.tmpl: include the partial when Gitea is on and issues != nil.
- CSS: matches the Tasks section visual language.
main.go: GITEA_URL gates the integration (off when unset). GITEA_URL set
but GITEA_TOKEN missing → refuse to start.
deploy/dokploy.yaml: GITEA_URL env + GITEA_TOKEN secret added.
docs/design.md: new §6 mirroring §5's structure (link model, listing
semantics, caching, env contract, parked items).
67 lines
1.6 KiB
Go
67 lines
1.6 KiB
Go
package web
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/m/projax/gitea"
|
|
)
|
|
|
|
func TestIssueCacheTTL(t *testing.T) {
|
|
c := newIssueCache(50 * time.Millisecond)
|
|
c.set("k", []gitea.Issue{{Number: 1, Title: "a"}})
|
|
if got, ok := c.get("k"); !ok || len(got) != 1 {
|
|
t.Fatalf("immediate get: ok=%v got=%v", ok, got)
|
|
}
|
|
time.Sleep(80 * time.Millisecond)
|
|
if _, ok := c.get("k"); ok {
|
|
t.Errorf("expected miss after TTL, got hit")
|
|
}
|
|
}
|
|
|
|
func TestRelativeTime(t *testing.T) {
|
|
now := time.Date(2026, 5, 15, 12, 0, 0, 0, time.UTC)
|
|
for _, tc := range []struct {
|
|
offset time.Duration
|
|
want string
|
|
}{
|
|
{-30 * time.Second, "just now"},
|
|
{-5 * time.Minute, "5m ago"},
|
|
{-3 * time.Hour, "3h ago"},
|
|
{-30 * time.Hour, "yesterday"},
|
|
{-5 * 24 * time.Hour, "5d ago"},
|
|
} {
|
|
got := relativeTime(now, now.Add(tc.offset))
|
|
if got != tc.want {
|
|
t.Errorf("offset=%v got %q want %q", tc.offset, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToViewPreservesFields(t *testing.T) {
|
|
in := []gitea.Issue{
|
|
{
|
|
Number: 7,
|
|
Title: "x",
|
|
State: "open",
|
|
Labels: []string{"bug"},
|
|
Assignees: []string{"a"},
|
|
Milestone: "M",
|
|
HTMLURL: "https://example/7",
|
|
UpdatedAt: time.Date(2026, 5, 14, 12, 0, 0, 0, time.UTC),
|
|
},
|
|
}
|
|
now := time.Date(2026, 5, 15, 12, 0, 0, 0, time.UTC)
|
|
got := toView(in, now)
|
|
if len(got) != 1 {
|
|
t.Fatalf("expected 1, got %d", len(got))
|
|
}
|
|
v := got[0]
|
|
if v.Number != 7 || v.Title != "x" || v.HTMLURL != "https://example/7" {
|
|
t.Errorf("field mismatch: %+v", v)
|
|
}
|
|
if v.UpdatedRel != "yesterday" {
|
|
t.Errorf("UpdatedRel = %q, want yesterday", v.UpdatedRel)
|
|
}
|
|
}
|