293 lines
8.0 KiB
Go
293 lines
8.0 KiB
Go
package vault
|
|
|
|
import (
|
|
"errors"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func testModel() Model {
|
|
return Model{
|
|
Entries: []Entry{
|
|
{ID: "1", Title: "Dynadot", Username: "jjulian", URL: "https://www.dynadot.com", Path: []string{"Joe", "Internet"}},
|
|
{ID: "2", Title: "Git Server", Username: "joejulian", URL: "https://git.julianfamily.org", Path: []string{"Joe", "Internet"}},
|
|
{ID: "3", Title: "Home Assistant (Codex)", Username: "codex", URL: "https://lights.julianfamily.org", Path: []string{"Joe", "Home Assistant"}},
|
|
{ID: "4", Title: "Alma (WA Prep)", Username: "christina.julian", URL: "https://waprep.getalma.com", Path: []string{"Tricia", "School"}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestChildGroupsReturnsImmediateGroupsOnly(t *testing.T) {
|
|
model := testModel()
|
|
|
|
got := model.ChildGroups([]string{"Joe"})
|
|
want := []string{"Home Assistant", "Internet"}
|
|
|
|
if !slices.Equal(got, want) {
|
|
t.Fatalf("ChildGroups() = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEntriesInPathReturnsOnlyDirectEntries(t *testing.T) {
|
|
model := testModel()
|
|
|
|
got := model.EntriesInPath([]string{"Joe", "Internet"})
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(EntriesInPath()) = %d, want 2", len(got))
|
|
}
|
|
|
|
if got[0].Title != "Dynadot" || got[1].Title != "Git Server" {
|
|
t.Fatalf("EntriesInPath() titles = %q, %q", got[0].Title, got[1].Title)
|
|
}
|
|
}
|
|
|
|
func TestSearchReturnsMatchesWithFullPathContext(t *testing.T) {
|
|
model := testModel()
|
|
|
|
got := model.Search("git")
|
|
if len(got) != 1 {
|
|
t.Fatalf("len(Search()) = %d, want 1", len(got))
|
|
}
|
|
|
|
if got[0].Entry.Title != "Git Server" {
|
|
t.Fatalf("Search() title = %q, want %q", got[0].Entry.Title, "Git Server")
|
|
}
|
|
|
|
if got[0].Path != "Joe / Internet" {
|
|
t.Fatalf("Search() path = %q, want %q", got[0].Path, "Joe / Internet")
|
|
}
|
|
}
|
|
|
|
func TestTemplateEntriesAreStoredSeparatelyFromNormalEntries(t *testing.T) {
|
|
model := testModel()
|
|
|
|
model.UpsertTemplate(Entry{
|
|
ID: "tpl-1",
|
|
Title: "Website Login",
|
|
Username: "template-user",
|
|
Password: "template-password",
|
|
URL: "https://example.com",
|
|
Notes: "Reusable template for website accounts.",
|
|
Tags: []string{"template", "web"},
|
|
Path: []string{"Templates"},
|
|
})
|
|
|
|
if len(model.Entries) != 4 {
|
|
t.Fatalf("len(Entries) = %d, want 4", len(model.Entries))
|
|
}
|
|
|
|
if len(model.Templates) != 1 {
|
|
t.Fatalf("len(Templates) = %d, want 1", len(model.Templates))
|
|
}
|
|
|
|
if got := model.Templates[0].Title; got != "Website Login" {
|
|
t.Fatalf("Templates[0].Title = %q, want %q", got, "Website Login")
|
|
}
|
|
}
|
|
|
|
func TestInstantiateTemplateCreatesNormalEntryWithOverrides(t *testing.T) {
|
|
model := Model{
|
|
Templates: []Entry{
|
|
{
|
|
ID: "tpl-1",
|
|
Title: "Website Login",
|
|
Username: "template-user",
|
|
Password: "template-password",
|
|
URL: "https://example.com",
|
|
Notes: "Reusable template for website accounts.",
|
|
Tags: []string{"template", "web"},
|
|
Fields: map[string]string{
|
|
"Environment": "prod",
|
|
},
|
|
Path: []string{"Templates"},
|
|
},
|
|
},
|
|
}
|
|
|
|
entry, err := model.InstantiateTemplate("tpl-1", Entry{
|
|
ID: "entry-1",
|
|
Title: "Dynadot",
|
|
Username: "jjulian",
|
|
Password: "hunter2",
|
|
URL: "https://www.dynadot.com",
|
|
Path: []string{"Joe", "Internet"},
|
|
Tags: []string{"dns"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("InstantiateTemplate() error = %v", err)
|
|
}
|
|
|
|
if entry.ID != "entry-1" {
|
|
t.Fatalf("entry.ID = %q, want %q", entry.ID, "entry-1")
|
|
}
|
|
|
|
if entry.Title != "Dynadot" {
|
|
t.Fatalf("entry.Title = %q, want %q", entry.Title, "Dynadot")
|
|
}
|
|
|
|
if entry.Username != "jjulian" || entry.Password != "hunter2" || entry.URL != "https://www.dynadot.com" {
|
|
t.Fatalf("entry credentials = %#v, want override values", entry)
|
|
}
|
|
|
|
if entry.Notes != "Reusable template for website accounts." {
|
|
t.Fatalf("entry.Notes = %q, want %q", entry.Notes, "Reusable template for website accounts.")
|
|
}
|
|
|
|
if !slices.Equal(entry.Tags, []string{"dns"}) {
|
|
t.Fatalf("entry.Tags = %v, want [dns]", entry.Tags)
|
|
}
|
|
|
|
if entry.Fields["Environment"] != "prod" {
|
|
t.Fatalf("entry.Fields[Environment] = %q, want %q", entry.Fields["Environment"], "prod")
|
|
}
|
|
|
|
got := model.EntriesInPath([]string{"Joe", "Internet"})
|
|
if len(got) != 1 || got[0].Title != "Dynadot" {
|
|
t.Fatalf("EntriesInPath() = %#v, want instantiated Dynadot entry", got)
|
|
}
|
|
}
|
|
|
|
func TestInstantiateTemplateFailsForUnknownTemplate(t *testing.T) {
|
|
model := Model{}
|
|
|
|
_, err := model.InstantiateTemplate("missing-template", Entry{ID: "entry-1"})
|
|
if err == nil {
|
|
t.Fatal("InstantiateTemplate() error = nil, want ErrEntryNotFound")
|
|
}
|
|
|
|
if !errors.Is(err, ErrEntryNotFound) {
|
|
t.Fatalf("InstantiateTemplate() error = %v, want ErrEntryNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteTemplateRemovesTemplateWithoutTouchingEntries(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
model := Model{
|
|
Entries: []Entry{
|
|
{ID: "entry-1", Title: "Git Server", Path: []string{"Root", "Internet"}},
|
|
},
|
|
Templates: []Entry{
|
|
{ID: "tpl-1", Title: "Website Login", Path: []string{"Templates"}},
|
|
},
|
|
}
|
|
|
|
if err := model.DeleteTemplate("tpl-1"); err != nil {
|
|
t.Fatalf("DeleteTemplate() error = %v", err)
|
|
}
|
|
|
|
if len(model.Templates) != 0 {
|
|
t.Fatalf("len(Templates) = %d, want 0", len(model.Templates))
|
|
}
|
|
if len(model.Entries) != 1 || model.Entries[0].ID != "entry-1" {
|
|
t.Fatalf("Entries = %#v, want unchanged normal entry", model.Entries)
|
|
}
|
|
}
|
|
|
|
func TestMoveTemplateChangesItsPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
model := Model{
|
|
Templates: []Entry{
|
|
{ID: "tpl-1", Title: "Website Login", Path: []string{"Templates", "Web"}},
|
|
},
|
|
}
|
|
|
|
if err := model.MoveTemplate("tpl-1", []string{"Templates", "Infra"}); err != nil {
|
|
t.Fatalf("MoveTemplate() error = %v", err)
|
|
}
|
|
|
|
if got := model.Templates[0].Path; !slices.Equal(got, []string{"Templates", "Infra"}) {
|
|
t.Fatalf("Templates[0].Path = %v, want [Templates Infra]", got)
|
|
}
|
|
}
|
|
|
|
func TestDuplicateEntryCopiesEntryWithNewIDAndTitle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
model := Model{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "entry-1",
|
|
Title: "Git Server",
|
|
Username: "joejulian",
|
|
Password: "token-1",
|
|
Path: []string{"Root", "Internet"},
|
|
},
|
|
},
|
|
}
|
|
|
|
duplicate, err := model.DuplicateEntry("entry-1", "entry-2")
|
|
if err != nil {
|
|
t.Fatalf("DuplicateEntry() error = %v", err)
|
|
}
|
|
|
|
if duplicate.ID != "entry-2" {
|
|
t.Fatalf("duplicate.ID = %q, want %q", duplicate.ID, "entry-2")
|
|
}
|
|
if duplicate.Title != "Git Server (Copy)" {
|
|
t.Fatalf("duplicate.Title = %q, want %q", duplicate.Title, "Git Server (Copy)")
|
|
}
|
|
got := model.EntriesInPath([]string{"Root", "Internet"})
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(EntriesInPath()) = %d, want 2", len(got))
|
|
}
|
|
}
|
|
|
|
func TestCreateGroupMakesItVisibleAsChildGroup(t *testing.T) {
|
|
model := testModel()
|
|
|
|
model.CreateGroup([]string{"Joe"}, "Finance")
|
|
|
|
got := model.ChildGroups([]string{"Joe"})
|
|
want := []string{"Finance", "Home Assistant", "Internet"}
|
|
if !slices.Equal(got, want) {
|
|
t.Fatalf("ChildGroups() = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRenameGroupMovesEntriesAndKeepsHierarchy(t *testing.T) {
|
|
model := testModel()
|
|
|
|
if err := model.RenameGroup([]string{"Joe", "Internet"}, "Infra"); err != nil {
|
|
t.Fatalf("RenameGroup() error = %v", err)
|
|
}
|
|
|
|
got := model.EntriesInPath([]string{"Joe", "Infra"})
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(EntriesInPath(Joe/Infra)) = %d, want 2", len(got))
|
|
}
|
|
|
|
if len(model.EntriesInPath([]string{"Joe", "Internet"})) != 0 {
|
|
t.Fatal("EntriesInPath(Joe/Internet) should be empty after rename")
|
|
}
|
|
}
|
|
|
|
func TestMoveEntryChangesItsPath(t *testing.T) {
|
|
model := testModel()
|
|
|
|
if err := model.MoveEntry("1", []string{"Tricia", "School"}); err != nil {
|
|
t.Fatalf("MoveEntry() error = %v", err)
|
|
}
|
|
|
|
got := model.EntriesInPath([]string{"Tricia", "School"})
|
|
if len(got) != 2 {
|
|
t.Fatalf("len(EntriesInPath(Tricia/School)) = %d, want 2", len(got))
|
|
}
|
|
}
|
|
|
|
func TestDeleteEmptyGroupRemovesItFromNavigation(t *testing.T) {
|
|
model := testModel()
|
|
|
|
model.CreateGroup([]string{"Joe"}, "Finance")
|
|
if err := model.DeleteGroup([]string{"Joe", "Finance"}); err != nil {
|
|
t.Fatalf("DeleteGroup() error = %v", err)
|
|
}
|
|
|
|
got := model.ChildGroups([]string{"Joe"})
|
|
want := []string{"Home Assistant", "Internet"}
|
|
if !slices.Equal(got, want) {
|
|
t.Fatalf("ChildGroups() = %v, want %v", got, want)
|
|
}
|
|
}
|