package inventory import ( "context" "errors" "strings" "testing" ) // The agent account (novox/hq ADR 0266): recorded and read back with every node, its home derived when // not stated, cleared by an empty name — and refused when it is root, the operator's own account, or no // login at all, because each of those would say agents have an account of their own while they do not. func TestAgentAccountIsRecordedAndRefusedWhereItWouldNotConfine(t *testing.T) { inv := ForTest(t) ctx := context.Background() if _, err := inv.AddNode(ctx, "anchor"); err != nil { t.Fatal(err) } if err := inv.SetAccount(ctx, "anchor", "operator", ""); err != nil { t.Fatal(err) } n, err := inv.NodeByName(ctx, "anchor") if err != nil { t.Fatal(err) } if n.AgentAccount != "" || n.AgentHome() != "" { t.Fatalf("a node that names none has agent account %q, home %q", n.AgentAccount, n.AgentHome()) } if err := inv.SetAgentAccount(ctx, "anchor", "agent", ""); err != nil { t.Fatal(err) } n, _ = inv.NodeByName(ctx, "anchor") if n.AgentAccount != "agent" || n.AgentHome() != "/home/agent" { t.Fatalf("agent account %q, home %q; want agent, /home/agent", n.AgentAccount, n.AgentHome()) } all, err := inv.Nodes(ctx) if err != nil || len(all) != 1 || all[0].AgentAccount != "agent" { t.Fatalf("the listing does not carry the agent account: %+v %v", all, err) } if err := inv.SetAgentAccount(ctx, "anchor", "agent", "/srv/agent"); err != nil { t.Fatal(err) } if n, _ = inv.NodeByName(ctx, "anchor"); n.AgentHome() != "/srv/agent" { t.Fatalf("the stated home is %q", n.AgentHome()) } for _, c := range []struct{ account, home, says string }{ {"root", "", "may not run as root"}, {"operator", "", "operator account"}, {"Agent", "", "not a login name"}, {"9agent", "", "not a login name"}, {"agent", "relative", "absolute"}, {"postgres", "", "service account"}, {"systemd-network", "", "service account"}, {"showcase", "", "service account"}, {"", "/home/x", "without an agent account"}, } { err := inv.SetAgentAccount(ctx, "anchor", c.account, c.home) if err == nil || !strings.Contains(err.Error(), c.says) { t.Errorf("%q %q: %v; want a refusal saying %q", c.account, c.home, err, c.says) } } if n, _ = inv.NodeByName(ctx, "anchor"); n.AgentAccount != "agent" { t.Fatalf("a refusal changed the record: %q", n.AgentAccount) } // The other direction: the operator account may not be named as the agent account either. if err := inv.SetAccount(ctx, "anchor", "agent", ""); err == nil || !strings.Contains(err.Error(), "agent account") { t.Fatalf("the operator account named as the agent account: %v; want a refusal", err) } if n, _ = inv.NodeByName(ctx, "anchor"); n.Account != "operator" { t.Fatalf("a refusal changed the operator account: %q", n.Account) } if err := inv.SetAgentAccount(ctx, "anchor", "", ""); err != nil { t.Fatal(err) } if err := inv.SetAccount(ctx, "anchor", "agent", ""); err != nil { t.Fatalf("with the agent account cleared, the name is free: %v", err) } if err := inv.SetAccount(ctx, "anchor", "operator", ""); err != nil { t.Fatal(err) } if n, _ = inv.NodeByName(ctx, "anchor"); n.AgentAccount != "" || n.AgentAccountHome != "" { t.Fatalf("clearing left %q %q", n.AgentAccount, n.AgentAccountHome) } if err := inv.SetAgentAccount(ctx, "nowhere", "agent", ""); !errors.Is(err, ErrNoSuchNode) { t.Fatalf("an unknown node: %v", err) } }