Files
magpie/internal/davsync/unreadable_test.go
T
yetone 9c54a560cc provider: a providers.json that can't be read is said where the providers are listed, never backed up or synced as none, and is replaced through a rename (PR #415's review) — #415 made edits refuse a file they couldn't read or decode, but every read for the user still took it as empty: magpie providers said "no providers yet", magpie models "no models yet", the Providers page opened the Add sheet as for a first use, and backup.Collect, so an export and a WebDAV/S3 sync, carried it as no providers, which another computer's Mirror would then take as every provider removed. magpie providers and magpie models now list what is left (the signed-in accounts) and end in the error, the TUI says it over its list, and the Providers page shows a "Your providers file can't be read" box (无法读取供应商配置文件) over the list with magpie's reason and no Add sheet; start-up, the gateway and the catalog still go on with it as empty. Stored and StoredGroups return the read error, so a backup and a sync stop on it with nothing pushed. The error, for reads and refused writes alike, names the file, says magpie left it unchanged and to fix it or move it aside (errors.Is ErrUnreadable). store writes through writePrivate, a 0600 temp file renamed over it (steady.Rename waits out a Windows reader holding it, a symlink keeps its target), instead of os.WriteFile's truncate-then-write, and the read goes through steady.ReadFile. TestFileErrorSaysUnreadable, TestWriteErrorsSayLeftUnchanged, TestStoredRefusesUnreadable, TestStoreReplacesAtomically, TestSyncStopsOnUnreadableProviders, TestProvidersCmdSaysUnreadable and provider-file-error.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; provider-file-error, click-scroll, old-webkit, providers-off-fold, add-sheet pass.
2026-10-01 19:45:24 +08:00

84 lines
2.4 KiB
Go

package davsync
import (
"bytes"
"context"
"errors"
"net/http/httptest"
"os"
"slices"
"testing"
"github.com/yetone/magpie/internal/backup"
"github.com/yetone/magpie/internal/provider"
)
// A providers.json that can't be read is never pushed as no providers: a
// sync stops on it, the server keeps the providers, and another computer
// mirroring the server keeps them too.
func TestSyncStopsOnUnreadableProviders(t *testing.T) {
fake := &fakeDAV{files: map[string][]byte{}, etags: map[string]string{}, dirs: map[string]bool{"/dav": true}}
srv := httptest.NewServer(fake)
defer srv.Close()
ctx := context.Background()
cfg := Config{URL: srv.URL + "/dav/", User: "me", Password: "pw", Passphrase: "correct horse", Keys: true}
a, b := newComputer(t), newComputer(t)
b.use(t)
if err := Configure(cfg); err != nil {
t.Fatal(err)
}
a.use(t)
if err := provider.Save(provider.Provider{ID: "deepseek", Name: "DeepSeek", Chat: "https://api.deepseek.com/v1", Key: "k1"}); err != nil {
t.Fatal(err)
}
if err := Configure(cfg); err != nil {
t.Fatal(err)
}
if err := Now(ctx); err != nil {
t.Fatal(err)
}
b.use(t)
if err := Now(ctx); err != nil {
t.Fatal(err)
}
if got := ids(); !slices.Equal(got, []string{"deepseek=k1"}) {
t.Fatalf("b's providers: %v", got)
}
// a's file is cut short
a.use(t)
whole, err := os.ReadFile(provider.Path())
if err != nil {
t.Fatal(err)
}
broken := whole[:len(whole)/2]
if err := os.WriteFile(provider.Path(), broken, 0o600); err != nil {
t.Fatal(err)
}
puts := fake.puts
if err := Now(ctx); !errors.Is(err, provider.ErrUnreadable) {
t.Fatalf("a synced a broken providers.json: %v", err)
}
if fake.puts != puts {
t.Fatal("a pushed a broken providers.json")
}
if after, _ := os.ReadFile(provider.Path()); !bytes.Equal(after, broken) {
t.Fatal("a's broken providers.json was written over")
}
if _, err := backup.Collect(true, "test"); !errors.Is(err, provider.ErrUnreadable) {
t.Fatalf("a backup of a broken providers.json: %v", err)
}
remote, err := backup.Open(fake.files["/dav/magpie/magpie.magpie-backup"], "correct horse")
if err != nil || len(remote.Providers) != 1 {
t.Fatalf("on the server: %v %+v", err, remote.Providers)
}
b.use(t)
if err := Now(ctx); err != nil {
t.Fatal(err)
}
if got := ids(); !slices.Equal(got, []string{"deepseek=k1"}) {
t.Fatalf("b lost its providers to a's broken file: %v", got)
}
}