coverage: medium batch — hostnat runner, store guest-sessions, daemon helpers
Reuses existing fixtures (CommandRunner fakes, SQLite tempfile store, pure-Go seams). No new infra needed. hostnat 50% -> 98% (iptables orchestration via fake runner) store 78% -> 91% (guest_sessions CRUD roundtrip) daemon/session 57% -> 95% (script gen, state parse, snapshot apply) daemon/opstate 67% -> 100% (Registry Insert/Get/Prune) daemon (firstNonEmpty) slight bump Total 54.0% -> 56.5%.
This commit is contained in:
parent
f8979de58a
commit
346eaba673
5 changed files with 1010 additions and 0 deletions
214
internal/store/guest_session_test.go
Normal file
214
internal/store/guest_session_test.go
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"banger/internal/model"
|
||||
)
|
||||
|
||||
func sampleGuestSession(id, vmID, name string) model.GuestSession {
|
||||
now := fixedTime()
|
||||
exit := 7
|
||||
return model.GuestSession{
|
||||
ID: id,
|
||||
VMID: vmID,
|
||||
Name: name,
|
||||
Backend: "ssh",
|
||||
AttachBackend: "vsock",
|
||||
AttachMode: "rpc",
|
||||
Command: "pi",
|
||||
Args: []string{"--mode", "rpc"},
|
||||
CWD: "/root/repo",
|
||||
Env: map[string]string{"FOO": "bar"},
|
||||
StdinMode: model.GuestSessionStdinMode("pipe"),
|
||||
Status: model.GuestSessionStatus("exited"),
|
||||
ExitCode: &exit,
|
||||
GuestPID: 1234,
|
||||
GuestStateDir: "/tmp/guest-" + id,
|
||||
StdoutLogPath: "/tmp/" + id + ".stdout",
|
||||
StderrLogPath: "/tmp/" + id + ".stderr",
|
||||
Tags: map[string]string{"role": "planner"},
|
||||
LastError: "",
|
||||
Attachable: true,
|
||||
Reattachable: true,
|
||||
LaunchStage: "started",
|
||||
LaunchMessage: "ok",
|
||||
LaunchRawLog: "boot log...",
|
||||
CreatedAt: now,
|
||||
StartedAt: now,
|
||||
UpdatedAt: now,
|
||||
EndedAt: now.Add(time.Minute),
|
||||
}
|
||||
}
|
||||
|
||||
// openTestStoreWithVMs opens a fresh store seeded with the given VM IDs so
|
||||
// guest_sessions FK constraints are satisfied. Each VM gets a minimal
|
||||
// image it references.
|
||||
func openTestStoreWithVMs(t *testing.T, vmIDs ...string) *Store {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
store := openTestStore(t)
|
||||
|
||||
image := sampleImage("stub-image")
|
||||
if err := store.UpsertImage(ctx, image); err != nil {
|
||||
t.Fatalf("UpsertImage: %v", err)
|
||||
}
|
||||
for i, id := range vmIDs {
|
||||
vm := sampleVM(id, image.ID, fmt.Sprintf("172.16.0.%d", i+2))
|
||||
vm.ID = id
|
||||
if err := store.UpsertVM(ctx, vm); err != nil {
|
||||
t.Fatalf("UpsertVM(%s): %v", id, err)
|
||||
}
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func TestGuestSessionUpsertAndGetByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
store := openTestStoreWithVMs(t, "vm-1")
|
||||
|
||||
session := sampleGuestSession("sess-1", "vm-1", "planner")
|
||||
if err := store.UpsertGuestSession(ctx, session); err != nil {
|
||||
t.Fatalf("UpsertGuestSession: %v", err)
|
||||
}
|
||||
|
||||
got, err := store.GetGuestSessionByID(ctx, "sess-1")
|
||||
if err != nil {
|
||||
t.Fatalf("GetGuestSessionByID: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, session) {
|
||||
t.Fatalf("round-trip mismatch:\n got %+v\n want %+v", got, session)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuestSessionUpsertIsIdempotent(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
store := openTestStoreWithVMs(t, "vm-1")
|
||||
|
||||
session := sampleGuestSession("sess-1", "vm-1", "planner")
|
||||
if err := store.UpsertGuestSession(ctx, session); err != nil {
|
||||
t.Fatalf("UpsertGuestSession (first): %v", err)
|
||||
}
|
||||
|
||||
// Mutate + re-upsert → existing row updated.
|
||||
session.Command = "pi --other"
|
||||
session.Status = model.GuestSessionStatus("running")
|
||||
session.ExitCode = nil
|
||||
if err := store.UpsertGuestSession(ctx, session); err != nil {
|
||||
t.Fatalf("UpsertGuestSession (second): %v", err)
|
||||
}
|
||||
|
||||
got, err := store.GetGuestSessionByID(ctx, "sess-1")
|
||||
if err != nil {
|
||||
t.Fatalf("GetGuestSessionByID: %v", err)
|
||||
}
|
||||
if got.Command != "pi --other" {
|
||||
t.Errorf("command = %q, want 'pi --other'", got.Command)
|
||||
}
|
||||
if got.Status != model.GuestSessionStatus("running") {
|
||||
t.Errorf("status = %q, want running", got.Status)
|
||||
}
|
||||
if got.ExitCode != nil {
|
||||
t.Errorf("ExitCode = %v, want nil after clearing", got.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGuestSessionByIDOrName(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
store := openTestStoreWithVMs(t, "vm-1")
|
||||
|
||||
session := sampleGuestSession("sess-1", "vm-1", "planner")
|
||||
if err := store.UpsertGuestSession(ctx, session); err != nil {
|
||||
t.Fatalf("UpsertGuestSession: %v", err)
|
||||
}
|
||||
|
||||
byID, err := store.GetGuestSession(ctx, "vm-1", "sess-1")
|
||||
if err != nil {
|
||||
t.Fatalf("GetGuestSession by ID: %v", err)
|
||||
}
|
||||
if byID.ID != "sess-1" {
|
||||
t.Errorf("by-ID: got %q, want sess-1", byID.ID)
|
||||
}
|
||||
|
||||
byName, err := store.GetGuestSession(ctx, "vm-1", "planner")
|
||||
if err != nil {
|
||||
t.Fatalf("GetGuestSession by name: %v", err)
|
||||
}
|
||||
if byName.Name != "planner" {
|
||||
t.Errorf("by-name: got %q, want planner", byName.Name)
|
||||
}
|
||||
|
||||
// Scoped to the VM.
|
||||
if _, err := store.GetGuestSession(ctx, "vm-unknown", "sess-1"); !errors.Is(err, sql.ErrNoRows) {
|
||||
t.Errorf("wrong-vm lookup = %v, want sql.ErrNoRows", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListGuestSessionsByVMOrdersByCreatedAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
store := openTestStoreWithVMs(t, "vm-1", "vm-2")
|
||||
|
||||
base := fixedTime()
|
||||
first := sampleGuestSession("sess-early", "vm-1", "first")
|
||||
first.CreatedAt = base
|
||||
second := sampleGuestSession("sess-late", "vm-1", "second")
|
||||
second.CreatedAt = base.Add(time.Hour)
|
||||
other := sampleGuestSession("sess-other", "vm-2", "other")
|
||||
|
||||
for _, s := range []model.GuestSession{second, first, other} {
|
||||
if err := store.UpsertGuestSession(ctx, s); err != nil {
|
||||
t.Fatalf("UpsertGuestSession: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sessions, err := store.ListGuestSessionsByVM(ctx, "vm-1")
|
||||
if err != nil {
|
||||
t.Fatalf("ListGuestSessionsByVM: %v", err)
|
||||
}
|
||||
if len(sessions) != 2 {
|
||||
t.Fatalf("len = %d, want 2 (vm-1 only)", len(sessions))
|
||||
}
|
||||
if sessions[0].ID != "sess-early" || sessions[1].ID != "sess-late" {
|
||||
t.Fatalf("order: got %q, %q; want sess-early, sess-late", sessions[0].ID, sessions[1].ID)
|
||||
}
|
||||
|
||||
empty, err := store.ListGuestSessionsByVM(ctx, "vm-unknown")
|
||||
if err != nil {
|
||||
t.Fatalf("ListGuestSessionsByVM (unknown vm): %v", err)
|
||||
}
|
||||
if len(empty) != 0 {
|
||||
t.Fatalf("unknown vm sessions = %+v, want empty", empty)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteGuestSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
store := openTestStoreWithVMs(t, "vm-1")
|
||||
|
||||
session := sampleGuestSession("sess-1", "vm-1", "planner")
|
||||
if err := store.UpsertGuestSession(ctx, session); err != nil {
|
||||
t.Fatalf("UpsertGuestSession: %v", err)
|
||||
}
|
||||
if err := store.DeleteGuestSession(ctx, "sess-1"); err != nil {
|
||||
t.Fatalf("DeleteGuestSession: %v", err)
|
||||
}
|
||||
if _, err := store.GetGuestSessionByID(ctx, "sess-1"); !errors.Is(err, sql.ErrNoRows) {
|
||||
t.Fatalf("after delete err = %v, want sql.ErrNoRows", err)
|
||||
}
|
||||
|
||||
// Deleting something that doesn't exist is a no-op (matches SQL DELETE semantics).
|
||||
if err := store.DeleteGuestSession(ctx, "sess-nope"); err != nil {
|
||||
t.Fatalf("DeleteGuestSession on missing row: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue