package catalogue import ( "strings" "testing" ) // One account, several modules: the shell's module sets its shell, the container runtime's adds it // to a group. Groups are only ever added by the host, so they are contributed; a shell or a home is // one value, owned by one module per node. func userResource(fields map[string]any) map[string]any { r := map[string]any{"id": "operator", "type": "user", "name": "op"} for k, v := range fields { r[k] = v } return r } func TestAShellAndAGroupOnOneAccountFromTwoModulesResolve(t *testing.T) { zsh := Manifest{Module: "zsh", Resources: []map[string]any{userResource(map[string]any{"shell": "/usr/bin/zsh"})}} docker := Manifest{Module: "docker", Resources: []map[string]any{userResource(map[string]any{"groups": []any{"docker"}})}} other := Manifest{Module: "media", Resources: []map[string]any{userResource(map[string]any{"groups": []any{"video"}})}} if problems := checkResources([]Manifest{zsh, docker, other}); len(problems) != 0 { t.Fatalf("a shell and two modules' groups on one account were refused: %v", problems) } if _, err := Resolve(shelf(zsh, docker, other), []string{"zsh", "docker", "media"}, workstation(), World{}); err != nil { t.Fatalf("the three did not resolve together: %v", err) } } func TestTwoModulesSettingOneAccountsShellOrHomeAreRefused(t *testing.T) { for _, field := range []string{"shell", "home"} { a := Manifest{Module: "zsh", Resources: []map[string]any{userResource(map[string]any{field: "/one"})}} b := Manifest{Module: "fish", Resources: []map[string]any{userResource(map[string]any{field: "/two", "groups": []any{"x"}})}} problems := checkResources([]Manifest{a, b}) want := `zsh and fish both set the ` + field + ` of the user "op"` if len(problems) != 1 || !strings.Contains(problems[0], want) { t.Errorf("two modules setting %s gave %v, want %q", field, problems, want) } } } // An account a module declares only to put in a group is applied where it is written, after the package // that makes the group (novox/hq ADR 0252); one that says how it logs in still goes first (issue 213). func TestAnAccountOnlyGivenGroupsKeepsItsWrittenPlace(t *testing.T) { compose := func(raw string) []map[string]any { t.Helper() m, err := ParseManifest([]byte(raw)) if err != nil { t.Fatal(err) } out, err := Resolution{Node: "workstation", Modules: []Manifest{m}}.Declaration(Rendering{}) if err != nil { t.Fatal(err) } return out } out := compose(`{"module": "lights", "version": "1", "resources": [ {"id": "daemon", "type": "package", "package": "lights-daemon"}, {"id": "account", "type": "user", "name": "op", "groups": ["lights"]} ]}`) if pkg, acct := indexOf(out, "lights.daemon"), indexOf(out, "lights.account"); pkg < 0 || acct < pkg { t.Fatalf("the account (%d) is not after the package that makes its group (%d): %v", acct, pkg, out) } out = compose(`{"module": "shell", "version": "1", "resources": [ {"id": "package", "type": "package", "package": "zsh"}, {"id": "login", "type": "user", "name": "op", "shell": "/usr/bin/zsh", "groups": ["wheel"]} ]}`) if pkg, acct := indexOf(out, "shell.package"), indexOf(out, "shell.login"); acct < 0 || acct > pkg { t.Fatalf("an account with a shell is no longer first (%d, package %d): %v", acct, pkg, out) } }