e871b05b38
CI / Test (linux/amd64) (pull_request) Successful in 34s
CI / Lint (pull_request) Failing after 16s
CI / Build (windows/amd64) (pull_request) Successful in 22s
CI / Build (linux/amd64) (pull_request) Successful in 20s
CI / Build (linux/arm64) (pull_request) Successful in 21s
Cleanup pass over the repo so CI can enforce lint going forward
without the only-new-issues escape hatch:
* gofumpt -w across the tree (31 hits, all formatting)
* misspell --fix (25 hits, US-locale spelling) — but reverted on
api.JobCancelled = "cancelled" since that literal is the wire +
DB CHECK constraint value, plus matched the case in store/fleet.go
back to "cancelled" and added //nolint:misspell on both for the
next time someone reaches for the auto-fix
* Wrap every `defer rows.Close()` / `defer stmt.Close()` /
`defer res.Body.Close()` in `defer func() { _ = .Close() }()`
to satisfy errcheck without losing the close itself
* websocket.Dial callers (1 prod, 4 tests) now capture + close the
upgrade response Body — coder/websocket can return res with a nil
Body on success, so the test deferred-closes guard against that
* Annotate the two genuine-by-design nilerr cases with //nolint
comments explaining why nil-on-error is the contract (cookie
missing = no session; ctx cancelled mid-backoff = clean shutdown)
* Add brief godoc on the 10 exported const groups + types that
revive flagged (api.HostOS/HostArch/JobKind/JobStatus/LogStream/
ErrorCode, restic.EventKind, store.Role, web.FS)
* Drop the unused (*Server).userByID method
* Inline the unparam baseView(active) — every UI page is under
the dashboard primary nav today
Result: `golangci-lint run ./...` reports 0 issues. CI lint job
no longer needs only-new-issues: true; X-06 follow-up entry in
tasks.md removed.
156 lines
4.5 KiB
Go
156 lines
4.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// makeSnapHost inserts a minimal host row that snapshot tests can hang
|
|
// off. Returns the host id.
|
|
func makeSnapHost(t *testing.T, s *Store) string {
|
|
t.Helper()
|
|
const id = "01HSNAPHOST00000000000000"
|
|
if err := s.CreateHost(context.Background(), Host{
|
|
ID: id, Name: "snap-host", OS: "linux", Arch: "amd64",
|
|
AgentVersion: "dev", ResticVersion: "0.16.0", ProtocolVersion: 1,
|
|
EnrolledAt: time.Now().UTC(),
|
|
}, "tokenhash", ""); err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
return id
|
|
}
|
|
|
|
func TestReplaceHostSnapshotsRoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
hostID := makeSnapHost(t, s)
|
|
ctx := context.Background()
|
|
|
|
now := time.Now().UTC().Truncate(time.Second)
|
|
in := []Snapshot{
|
|
{
|
|
ID: "deadbeef" + "00000000000000000000000000000000000000000000000000000000",
|
|
ShortID: "deadbeef",
|
|
Time: now.Add(-2 * time.Hour),
|
|
Hostname: "snap-host",
|
|
Paths: []string{"/etc", "/home"},
|
|
Tags: []string{"daily"},
|
|
SizeBytes: 4096, FileCount: 12,
|
|
},
|
|
{
|
|
ID: "cafef00d" + "00000000000000000000000000000000000000000000000000000000",
|
|
ShortID: "cafef00d",
|
|
Time: now.Add(-1 * time.Hour),
|
|
Hostname: "snap-host",
|
|
Paths: []string{"/etc"},
|
|
SizeBytes: 8192, FileCount: 24,
|
|
},
|
|
}
|
|
if err := s.ReplaceHostSnapshots(ctx, hostID, in, now); err != nil {
|
|
t.Fatalf("replace: %v", err)
|
|
}
|
|
|
|
out, err := s.ListSnapshotsByHost(ctx, hostID)
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(out) != 2 {
|
|
t.Fatalf("want 2 snapshots, got %d", len(out))
|
|
}
|
|
// Ordered by time DESC — most recent first.
|
|
if out[0].ShortID != "cafef00d" {
|
|
t.Errorf("want most-recent first; got %q", out[0].ShortID)
|
|
}
|
|
if got := len(out[0].Paths); got != 1 {
|
|
t.Errorf("paths roundtrip lost: %v", out[0].Paths)
|
|
}
|
|
if out[1].Tags == nil || out[1].Tags[0] != "daily" {
|
|
t.Errorf("tags roundtrip lost: %v", out[1].Tags)
|
|
}
|
|
|
|
// Host snapshot_count is updated atomically.
|
|
h, err := s.GetHost(ctx, hostID)
|
|
if err != nil {
|
|
t.Fatalf("get host: %v", err)
|
|
}
|
|
if h.SnapshotCount != 2 {
|
|
t.Errorf("host snapshot_count = %d, want 2", h.SnapshotCount)
|
|
}
|
|
}
|
|
|
|
func TestReplaceHostSnapshotsIsAuthoritative(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
hostID := makeSnapHost(t, s)
|
|
ctx := context.Background()
|
|
|
|
mk := func(id, short string, tOff time.Duration) Snapshot {
|
|
return Snapshot{
|
|
ID: id, ShortID: short, Time: time.Now().UTC().Add(tOff),
|
|
Hostname: "snap-host", Paths: []string{"/x"},
|
|
}
|
|
}
|
|
first := []Snapshot{
|
|
mk("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaa", -3*time.Hour),
|
|
mk("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "bbbbbbbb", -2*time.Hour),
|
|
mk("cccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "cccccccc", -1*time.Hour),
|
|
}
|
|
if err := s.ReplaceHostSnapshots(ctx, hostID, first, time.Now().UTC()); err != nil {
|
|
t.Fatalf("replace 1: %v", err)
|
|
}
|
|
|
|
// Subsequent forget+prune on the host: only one snapshot remains.
|
|
second := []Snapshot{
|
|
mk("cccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "cccccccc", -1*time.Hour),
|
|
}
|
|
if err := s.ReplaceHostSnapshots(ctx, hostID, second, time.Now().UTC()); err != nil {
|
|
t.Fatalf("replace 2: %v", err)
|
|
}
|
|
|
|
out, err := s.ListSnapshotsByHost(ctx, hostID)
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(out) != 1 || out[0].ShortID != "cccccccc" {
|
|
t.Errorf("after second replace, want [cccccccc], got %+v", out)
|
|
}
|
|
h, _ := s.GetHost(ctx, hostID)
|
|
if h.SnapshotCount != 1 {
|
|
t.Errorf("snapshot_count should track replacement: %d", h.SnapshotCount)
|
|
}
|
|
}
|
|
|
|
func TestReplaceHostSnapshotsEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
s := openTestStore(t)
|
|
hostID := makeSnapHost(t, s)
|
|
ctx := context.Background()
|
|
|
|
// First a non-empty replace.
|
|
if err := s.ReplaceHostSnapshots(ctx, hostID, []Snapshot{
|
|
{
|
|
ID: "1111111111111111111111111111111111111111111111111111111111111111",
|
|
ShortID: "11111111", Time: time.Now().UTC(), Hostname: "snap-host",
|
|
Paths: []string{"/x"},
|
|
},
|
|
}, time.Now().UTC()); err != nil {
|
|
t.Fatalf("replace 1: %v", err)
|
|
}
|
|
// Then empty — host has been wiped.
|
|
if err := s.ReplaceHostSnapshots(ctx, hostID, nil, time.Now().UTC()); err != nil {
|
|
t.Fatalf("replace empty: %v", err)
|
|
}
|
|
out, err := s.ListSnapshotsByHost(ctx, hostID)
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(out) != 0 {
|
|
t.Errorf("want empty, got %d", len(out))
|
|
}
|
|
h, _ := s.GetHost(ctx, hostID)
|
|
if h.SnapshotCount != 0 {
|
|
t.Errorf("snapshot_count should reset to 0, got %d", h.SnapshotCount)
|
|
}
|
|
}
|