test: OTP 算法 + 加密 + Config + QR 导出 单元测试
- authenticator: HOTP RFC 4226 向量、Base32 往返、SecretData 往返 - crypto: WAGO1 加密解密往返、错误密码、空密码、无效载荷 - config: YAML 明文/加密读写、密码错误、.bak 轮换 - qr: EntryToOtpAuth 各 vendor、URI 往返、默认值省略
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSaveLoadPlaintext(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "test.yaml")
|
||||
|
||||
cfg := &Config{
|
||||
Version: 1,
|
||||
Language: "en",
|
||||
Theme: "dark",
|
||||
Entries: []Entry{
|
||||
{Name: "Google", Vendor: "google", SecretRaw: "AABB\t6\tSHA1\t30"},
|
||||
{Name: "HOTP", Vendor: "hotp", SecretRaw: "CCDD\t6\tSHA1\t30|5"},
|
||||
},
|
||||
}
|
||||
if err := SaveYAML(cfg, path, nil); err != nil {
|
||||
t.Fatalf("SaveYAML: %v", err)
|
||||
}
|
||||
|
||||
loaded, err := LoadYAML(path, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadYAML: %v", err)
|
||||
}
|
||||
if loaded.Version != 1 {
|
||||
t.Errorf("Version = %d, want 1", loaded.Version)
|
||||
}
|
||||
if len(loaded.Entries) != 2 {
|
||||
t.Fatalf("entries = %d, want 2", len(loaded.Entries))
|
||||
}
|
||||
if loaded.Entries[0].Name != "Google" {
|
||||
t.Errorf("Entry[0].Name = %q, want %q", loaded.Entries[0].Name, "Google")
|
||||
}
|
||||
if loaded.Entries[1].Vendor != "hotp" {
|
||||
t.Errorf("Entry[1].Vendor = %q, want %q", loaded.Entries[1].Vendor, "hotp")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveLoadEncrypted(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "test.yaml")
|
||||
pw := []byte("s3cr3t")
|
||||
|
||||
cfg := &Config{
|
||||
Version: 1,
|
||||
Encrypted: true,
|
||||
Entries: []Entry{
|
||||
{Name: "Steam", Vendor: "steam", SecretRaw: "EEFF\t5\tSHA1\t30|SERIAL|DEVICE"},
|
||||
},
|
||||
}
|
||||
if err := SaveYAML(cfg, path, pw); err != nil {
|
||||
t.Fatalf("SaveYAML: %v", err)
|
||||
}
|
||||
|
||||
// Load without password → ErrPasswordRequired
|
||||
if _, err := LoadYAML(path, nil); err != ErrPasswordRequired {
|
||||
t.Errorf("expected ErrPasswordRequired, got %v", err)
|
||||
}
|
||||
|
||||
// Load with wrong password → ErrPasswordWrong
|
||||
if _, err := LoadYAML(path, []byte("wrong")); err != ErrPasswordWrong {
|
||||
t.Errorf("expected ErrPasswordWrong, got %v", err)
|
||||
}
|
||||
|
||||
// Load with correct password
|
||||
loaded, err := LoadYAML(path, pw)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadYAML: %v", err)
|
||||
}
|
||||
if len(loaded.Entries) != 1 {
|
||||
t.Fatalf("entries = %d, want 1", len(loaded.Entries))
|
||||
}
|
||||
if loaded.Entries[0].Name != "Steam" {
|
||||
t.Errorf("Entry[0].Name = %q, want %q", loaded.Entries[0].Name, "Steam")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupRotation(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "test.yaml")
|
||||
|
||||
// First save: no .bak exists yet
|
||||
cfg1 := &Config{Version: 1, Entries: []Entry{{Name: "A"}}}
|
||||
if err := SaveYAML(cfg1, path, nil); err != nil {
|
||||
t.Fatalf("SaveYAML #1: %v", err)
|
||||
}
|
||||
|
||||
// Second save: .bak should be created
|
||||
cfg2 := &Config{Version: 1, Entries: []Entry{{Name: "B"}}}
|
||||
if err := SaveYAML(cfg2, path, nil); err != nil {
|
||||
t.Fatalf("SaveYAML #2: %v", err)
|
||||
}
|
||||
|
||||
bak := path + ".bak"
|
||||
if _, err := os.Stat(bak); os.IsNotExist(err) {
|
||||
t.Error("expected .bak file to exist after second save")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadNonexistent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nonexistent.yaml")
|
||||
_, err := LoadYAML(path, nil)
|
||||
if err == nil {
|
||||
t.Error("expected error for nonexistent file")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user