test: OTP 算法 + 加密 + Config + QR 导出 单元测试

- authenticator: HOTP RFC 4226 向量、Base32 往返、SecretData 往返
- crypto: WAGO1 加密解密往返、错误密码、空密码、无效载荷
- config: YAML 明文/加密读写、密码错误、.bak 轮换
- qr: EntryToOtpAuth 各 vendor、URI 往返、默认值省略
This commit is contained in:
2026-06-12 10:50:25 +08:00
parent fd4d756823
commit cb4d88ebdd
4 changed files with 516 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
package qr
import (
"strings"
"testing"
"git.wxccs.org/iceking2nd/winauth-go/internal/config"
)
func TestEntryToOtpAuthGoogle(t *testing.T) {
e := config.Entry{
Name: "MyGoogle",
Vendor: "google",
SecretRaw: "0102030405\t6\tSHA1\t30",
}
oa, partial, err := EntryToOtpAuth(e)
if err != nil {
t.Fatalf("EntryToOtpAuth: %v", err)
}
if partial {
t.Error("Google should not be partial")
}
if oa.Type != "totp" {
t.Errorf("Type = %q, want totp", oa.Type)
}
if oa.Issuer != "Google" {
t.Errorf("Issuer = %q, want Google", oa.Issuer)
}
if oa.Label != "Google:MyGoogle" {
t.Errorf("Label = %q", oa.Label)
}
if oa.Digits != 6 {
t.Errorf("Digits = %d, want 6", oa.Digits)
}
if oa.Algorithm != "SHA1" {
t.Errorf("Algorithm = %q, want SHA1", oa.Algorithm)
}
}
func TestEntryToOtpAuthHOTP(t *testing.T) {
e := config.Entry{
Name: "Counter",
Vendor: "hotp",
SecretRaw: "AABBCCDD\t6\tSHA1\t30|10",
}
oa, _, err := EntryToOtpAuth(e)
if err != nil {
t.Fatalf("EntryToOtpAuth: %v", err)
}
if oa.Type != "hotp" {
t.Errorf("Type = %q, want hotp", oa.Type)
}
if oa.Counter != 10 {
t.Errorf("Counter = %d, want 10", oa.Counter)
}
}
func TestEntryToOtpAuthBattleNetPartial(t *testing.T) {
e := config.Entry{
Name: "BNet",
Vendor: "battlenet",
SecretRaw: "AABB\t8\tSHA1\t30|53455249414C",
}
oa, partial, err := EntryToOtpAuth(e)
if err != nil {
t.Fatalf("EntryToOtpAuth: %v", err)
}
if !partial {
t.Error("Battle.Net should be partial")
}
if oa.Digits != 8 {
t.Errorf("Digits = %d, want 8", oa.Digits)
}
}
func TestEntryToOtpAuthSteamPartial(t *testing.T) {
e := config.Entry{
Name: "Steam",
Vendor: "steam",
SecretRaw: "CCDD\t5\tSHA1\t30|73657269616C|646576696365",
}
_, partial, err := EntryToOtpAuth(e)
if err != nil {
t.Fatalf("EntryToOtpAuth: %v", err)
}
if !partial {
t.Error("Steam should be partial")
}
}
func TestEntryToOtpAuthEmptySecret(t *testing.T) {
e := config.Entry{Name: "Empty", Vendor: "google", SecretRaw: ""}
if _, _, err := EntryToOtpAuth(e); err == nil {
t.Error("expected error for empty secret")
}
}
func TestURIRoundTrip(t *testing.T) {
e := config.Entry{
Name: "Test",
Vendor: "google",
SecretRaw: "0102030405\t6\tSHA1\t30",
}
oa, _, err := EntryToOtpAuth(e)
if err != nil {
t.Fatalf("EntryToOtpAuth: %v", err)
}
uri := oa.URI()
// Parse it back
parsed, err := ParseOtpAuth(uri)
if err != nil {
t.Fatalf("ParseOtpAuth(%q): %v", uri, err)
}
if parsed.Type != "totp" {
t.Errorf("Type = %q, want totp", parsed.Type)
}
if parsed.SecretBase32 != oa.SecretBase32 {
t.Errorf("Secret mismatch: %q vs %q", parsed.SecretBase32, oa.SecretBase32)
}
}
func TestURIHOTP(t *testing.T) {
oa := &OtpAuth{
Type: "hotp",
Label: "MyHOTP",
SecretBase32: "JBSWY3DPEHPK3PXP",
Issuer: "Test",
Digits: 6,
Counter: 42,
}
uri := oa.URI()
if !strings.Contains(uri, "counter=42") {
t.Errorf("URI missing counter: %s", uri)
}
if !strings.HasPrefix(uri, "otpauth://hotp/") {
t.Errorf("URI wrong prefix: %s", uri)
}
}
func TestURIDefaultsOmitted(t *testing.T) {
oa := &OtpAuth{
Type: "totp",
SecretBase32: "AAAA",
Algorithm: "SHA1",
Digits: 6,
Period: 30,
}
uri := oa.URI()
if strings.Contains(uri, "algorithm") {
t.Errorf("SHA1 should be omitted: %s", uri)
}
if strings.Contains(uri, "digits") {
t.Errorf("digits=6 should be omitted: %s", uri)
}
if strings.Contains(uri, "period") {
t.Errorf("period=30 should be omitted: %s", uri)
}
}