From 061e3b882b815aca19e46221eea6b3bbab7019ef Mon Sep 17 00:00:00 2001 From: samosvalishe Date: Sat, 12 Sep 2026 17:33:46 +0300 Subject: [PATCH] =?UTF-8?q?fix(clients):=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=8F=D1=82=D1=8C=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BF=D1=80=D0=B8=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B5=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B8=20=D0=B1=D0=B0=D0=B7=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/clientsdb/db.go | 17 +++++++++----- internal/clientsdb/db_test.go | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/internal/clientsdb/db.go b/internal/clientsdb/db.go index b1fb44f..49bf3fc 100644 --- a/internal/clientsdb/db.go +++ b/internal/clientsdb/db.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "maps" "net" "os" "sync" @@ -61,16 +62,18 @@ func (db *DB) Add(clientID, comment string) error { db.mu.Lock() defer db.mu.Unlock() - db.data.Clients[clientID] = ClientInfo{Comment: comment} - return db.save() + next := Data{Clients: maps.Clone(db.data.Clients)} + next.Clients[clientID] = ClientInfo{Comment: comment} + return db.save(next) } func (db *DB) Remove(clientID string) error { db.mu.Lock() defer db.mu.Unlock() - delete(db.data.Clients, clientID) - return db.save() + next := Data{Clients: maps.Clone(db.data.Clients)} + delete(next.Clients, clientID) + return db.save(next) } func (db *DB) List() map[string]ClientInfo { @@ -126,8 +129,8 @@ func (db *DB) loadIfModified() { } } -func (db *DB) save() error { - b, err := json.MarshalIndent(db.data, "", " ") +func (db *DB) save(next Data) error { + b, err := json.MarshalIndent(next, "", " ") if err != nil { return err } @@ -138,6 +141,8 @@ func (db *DB) save() error { err = os.Rename(tmpFile, db.path) } if err == nil { + // Авторизация меняется только после успешной замены файла. + db.data = next stat, _ := os.Stat(db.path) if stat != nil { db.lastModified = stat.ModTime() diff --git a/internal/clientsdb/db_test.go b/internal/clientsdb/db_test.go index 45cd67c..adc79db 100644 --- a/internal/clientsdb/db_test.go +++ b/internal/clientsdb/db_test.go @@ -2,11 +2,55 @@ package clientsdb import ( "net" + "os" "path/filepath" "testing" "time" ) +func TestFailedSavePreservesAuthorization(t *testing.T) { + path := filepath.Join(t.TempDir(), "clients.json") + db, err := New(path) + if err != nil { + t.Fatal(err) + } + if err := db.Add("existing", "original"); err != nil { + t.Fatal(err) + } + // Каталог вместо временного файла воспроизводит отказ записи без прав root. + if err := os.Mkdir(path+".tmp", 0o700); err != nil { + t.Fatal(err) + } + operations := []struct { + name string + run func() error + }{ + {"add", func() error { return db.Add("new", "new") }}, + {"update", func() error { return db.Add("existing", "changed") }}, + {"remove", func() error { return db.Remove("existing") }}, + } + for _, op := range operations { + t.Run(op.name, func(t *testing.T) { + if err := op.run(); err == nil { + t.Fatal("save unexpectedly succeeded") + } + if !db.IsAuthorized("existing") || db.IsAuthorized("new") { + t.Fatal("failed save changed authorization") + } + if got := db.List(); len(got) != 1 || got["existing"].Comment != "original" { + t.Fatalf("failed save changed clients: %+v", got) + } + }) + } + loaded, err := New(path) + if err != nil { + t.Fatal(err) + } + if got := loaded.List(); len(got) != 1 || got["existing"].Comment != "original" { + t.Fatalf("disk changed: %+v", got) + } +} + func TestClientsDB(t *testing.T) { tmpDir := t.TempDir() dbPath := filepath.Join(tmpDir, "clients.json")