Add attachment replace workflow UI
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package appstate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -11,6 +12,11 @@ import (
|
||||
|
||||
type Section string
|
||||
|
||||
var (
|
||||
ErrAttachmentAlreadyExists = errors.New("attachment already exists")
|
||||
ErrAttachmentNotFound = errors.New("attachment not found")
|
||||
)
|
||||
|
||||
const (
|
||||
SectionEntries Section = ""
|
||||
SectionTemplates Section = "templates"
|
||||
@@ -639,6 +645,36 @@ func (s *State) AddAttachmentToSelectedEntry(name string, content []byte) error
|
||||
if model.Entries[i].Attachments == nil {
|
||||
model.Entries[i].Attachments = map[string][]byte{}
|
||||
}
|
||||
if _, exists := model.Entries[i].Attachments[name]; exists {
|
||||
return ErrAttachmentAlreadyExists
|
||||
}
|
||||
model.Entries[i].Attachments[name] = append([]byte(nil), content...)
|
||||
session.Replace(model)
|
||||
s.Dirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
return vault.ErrEntryNotFound
|
||||
}
|
||||
|
||||
func (s *State) ReplaceAttachmentOnSelectedEntry(name string, content []byte) error {
|
||||
session, ok := s.Session.(MutableSession)
|
||||
if !ok {
|
||||
return fmt.Errorf("session is not mutable")
|
||||
}
|
||||
|
||||
model, err := session.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range model.Entries {
|
||||
if model.Entries[i].ID != s.SelectedEntryID {
|
||||
continue
|
||||
}
|
||||
if _, exists := model.Entries[i].Attachments[name]; !exists {
|
||||
return ErrAttachmentNotFound
|
||||
}
|
||||
model.Entries[i].Attachments[name] = append([]byte(nil), content...)
|
||||
session.Replace(model)
|
||||
s.Dirty = true
|
||||
@@ -663,6 +699,9 @@ func (s *State) DeleteAttachmentFromSelectedEntry(name string) error {
|
||||
if model.Entries[i].ID != s.SelectedEntryID {
|
||||
continue
|
||||
}
|
||||
if _, exists := model.Entries[i].Attachments[name]; !exists {
|
||||
return ErrAttachmentNotFound
|
||||
}
|
||||
delete(model.Entries[i].Attachments, name)
|
||||
if len(model.Entries[i].Attachments) == 0 {
|
||||
model.Entries[i].Attachments = nil
|
||||
|
||||
@@ -1056,6 +1056,76 @@ func TestAddAttachmentToSelectedEntryPersistsAndMarksDirty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAttachmentToSelectedEntryRejectsDuplicateNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := testVaultModel()
|
||||
model.Entries[0].Attachments = map[string][]byte{"token.txt": []byte("secret")}
|
||||
sess := &mutableStubSession{model: model}
|
||||
state := State{
|
||||
Session: sess,
|
||||
CurrentPath: []string{"Root", "Internet"},
|
||||
SelectedEntryID: "dynadot",
|
||||
}
|
||||
|
||||
err := state.AddAttachmentToSelectedEntry("token.txt", []byte("replacement"))
|
||||
if !errors.Is(err, ErrAttachmentAlreadyExists) {
|
||||
t.Fatalf("AddAttachmentToSelectedEntry() error = %v, want ErrAttachmentAlreadyExists", err)
|
||||
}
|
||||
|
||||
got, currentErr := sess.Current()
|
||||
if currentErr != nil {
|
||||
t.Fatalf("Current() error = %v", currentErr)
|
||||
}
|
||||
if string(got.Entries[0].Attachments["token.txt"]) != "secret" {
|
||||
t.Fatalf("attachment content = %q, want %q", got.Entries[0].Attachments["token.txt"], "secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceAttachmentOnSelectedEntryPersistsAndMarksDirty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := testVaultModel()
|
||||
model.Entries[0].Attachments = map[string][]byte{"token.txt": []byte("secret")}
|
||||
sess := &mutableStubSession{model: model}
|
||||
state := State{
|
||||
Session: sess,
|
||||
CurrentPath: []string{"Root", "Internet"},
|
||||
SelectedEntryID: "dynadot",
|
||||
}
|
||||
|
||||
if err := state.ReplaceAttachmentOnSelectedEntry("token.txt", []byte("replacement")); err != nil {
|
||||
t.Fatalf("ReplaceAttachmentOnSelectedEntry() error = %v", err)
|
||||
}
|
||||
|
||||
got, err := state.VisibleEntries()
|
||||
if err != nil {
|
||||
t.Fatalf("VisibleEntries() error = %v", err)
|
||||
}
|
||||
if string(got[0].Attachments["token.txt"]) != "replacement" {
|
||||
t.Fatalf("attachment content = %q, want %q", got[0].Attachments["token.txt"], "replacement")
|
||||
}
|
||||
if !state.Dirty {
|
||||
t.Fatal("Dirty = false, want true after ReplaceAttachmentOnSelectedEntry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceAttachmentOnSelectedEntryRequiresExistingAttachment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sess := &mutableStubSession{model: testVaultModel()}
|
||||
state := State{
|
||||
Session: sess,
|
||||
CurrentPath: []string{"Root", "Internet"},
|
||||
SelectedEntryID: "dynadot",
|
||||
}
|
||||
|
||||
err := state.ReplaceAttachmentOnSelectedEntry("token.txt", []byte("replacement"))
|
||||
if !errors.Is(err, ErrAttachmentNotFound) {
|
||||
t.Fatalf("ReplaceAttachmentOnSelectedEntry() error = %v, want ErrAttachmentNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAttachmentFromSelectedEntryPersistsAndMarksDirty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1084,6 +1154,22 @@ func TestDeleteAttachmentFromSelectedEntryPersistsAndMarksDirty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAttachmentFromSelectedEntryRequiresExistingAttachment(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sess := &mutableStubSession{model: testVaultModel()}
|
||||
state := State{
|
||||
Session: sess,
|
||||
CurrentPath: []string{"Root", "Internet"},
|
||||
SelectedEntryID: "dynadot",
|
||||
}
|
||||
|
||||
err := state.DeleteAttachmentFromSelectedEntry("token.txt")
|
||||
if !errors.Is(err, ErrAttachmentNotFound) {
|
||||
t.Fatalf("DeleteAttachmentFromSelectedEntry() error = %v, want ErrAttachmentNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
type mutableStubSession struct {
|
||||
model vault.Model
|
||||
err error
|
||||
|
||||
Reference in New Issue
Block a user