f9718e6077
CI / Test (rest) (pull_request) Successful in 40s
CI / Test (store) (pull_request) Successful in 42s
CI / Lint (pull_request) Successful in 11s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/arm64) (pull_request) Successful in 7s
CI / Build (linux/amd64) (pull_request) Successful in 8s
CI / Test (server-http) (pull_request) Successful in 1m32s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m26s
160 lines
4.7 KiB
Go
160 lines
4.7 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.
|
|
refreshedAt := time.Now().UTC().Truncate(time.Millisecond)
|
|
if err := s.ReplaceHostSnapshots(ctx, hostID, nil, refreshedAt); 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)
|
|
}
|
|
if h.SnapshotRefreshedAt == nil || !h.SnapshotRefreshedAt.Equal(refreshedAt) {
|
|
t.Errorf("empty projection refresh time = %v, want %s", h.SnapshotRefreshedAt, refreshedAt)
|
|
}
|
|
}
|