package syncer import ( "testing" "time" ) // TestParseCredentialNested 插件 OAuth 的嵌套形态(真实桶里就是这个形状)。 func TestParseCredentialNested(t *testing.T) { raw := []byte(`{ "account": { "enterpriseId": "", "nickname": "luckfun233", "uid": "0f000ecd-dfcc-4ccc-bd0c-7d3fd4e2b950" }, "auth": { "accessToken": "at", "domain": "www.workbuddy.ai", "expiresAt": 1822477774, "realm": "global", "refreshToken": "rt" } }`) c, ok := parseCredential(raw) if !ok { t.Fatal("嵌套形态应解析成功") } if c.UID != "0f000ecd-dfcc-4ccc-bd0c-7d3fd4e2b950" { t.Errorf("UID=%q", c.UID) } if c.Nickname != "luckfun233" { t.Errorf("Nickname=%q", c.Nickname) } if c.ExpiresAt != 1822477774 { t.Errorf("ExpiresAt=%d", c.ExpiresAt) } if !c.HasRefresh { t.Error("HasRefresh 应为 true") } if c.Realm != "global" { t.Errorf("Realm=%q want global", c.Realm) } } // TestParseCredentialFlat 手写 / 旧版的扁平形态。 func TestParseCredentialFlat(t *testing.T) { raw := []byte(`{"accessToken":"at","refreshToken":"rt","expiresAt":1794492000,"domain":"www.codebuddy.cn","uid":"u9","nickname":"13467880565"}`) c, ok := parseCredential(raw) if !ok { t.Fatal("扁平形态应解析成功") } if c.UID != "u9" || c.ExpiresAt != 1794492000 || !c.HasRefresh { t.Errorf("解析结果不对: %+v", c) } // 无显式 realm + CN 域 → cn if c.Realm != "cn" { t.Errorf("Realm=%q want cn", c.Realm) } } // TestParseCredentialRealmFallback realm 缺省时按 domain 后缀推断,最后回落 cn。 func TestParseCredentialRealmFallback(t *testing.T) { cases := []struct{ explicit, domain, want string }{ {"global", "www.codebuddy.cn", "global"}, // 显式优先于 domain {"", "www.workbuddy.ai", "global"}, {"", "workbuddy.ai", "global"}, {"", "www.codebuddy.cn", "cn"}, {"", "", "cn"}, // 老 CN 凭证零回归 } for _, tc := range cases { if got := resolveRealm(tc.explicit, tc.domain); got != tc.want { t.Errorf("resolveRealm(%q,%q)=%q want %q", tc.explicit, tc.domain, got, tc.want) } } } // TestParseCredentialRejectsJunk 解析不出来必须是 (false),而不是报错或误判—— // 对账会退化成按内容哈希比较,仍然能正确同步。 func TestParseCredentialRejectsJunk(t *testing.T) { bad := [][]byte{ nil, []byte(""), []byte("not json"), []byte("[]"), []byte(`{"account":{"uid":"u1"},"auth":{"refreshToken":"rt"}}`), // 缺 accessToken []byte(`{"refreshToken":"rt"}`), // 扁平形态缺 accessToken []byte(`{"auth":"not-an-object"}`), } for _, raw := range bad { if _, ok := parseCredential(raw); ok { t.Errorf("应判为不可用凭证: %q", raw) } } } // TestParseCredentialFlatEmptyAccessToken 空白 accessToken 同样不算凭证。 func TestParseCredentialFlatEmptyAccessToken(t *testing.T) { if _, ok := parseCredential([]byte(`{"accessToken":" ","uid":"u1"}`)); ok { t.Error("空白 accessToken 不应算作凭证") } } // TestAnnotateAuth Integrated:annotateAuth 只在能解析时才写标注。 func TestAnnotateAuth(t *testing.T) { good := newFileState("auths/a.json", []byte(`{"account":{"uid":"u1","nickname":"n"},"auth":{"accessToken":"at","expiresAt":123,"refreshToken":"rt"}}`), time.Time{}, catAuth) if !good.IsAuth || good.UID != "u1" || good.ExpiresAt != 123 { t.Errorf("标注失败: %+v", good) } bad := newFileState("auths/b.json", []byte(`{"hello":"world"}`), time.Time{}, catAuth) if bad.IsAuth { t.Error("非凭证文件不应被标为 auth") } if bad.UID != "" || bad.ExpiresAt != 0 { t.Errorf("非凭证文件不该有 auth 标注: %+v", bad) } } // TestPathAllowedUsesIncludeData 复检白名单与 include_data 的联动(避免回归)。 func TestPathAllowedUsesIncludeData(t *testing.T) { if !PathAllowed("data/state.json", []string{"state.json"}) { t.Error("state.json 在白名单里却未被允许") } if PathAllowed("data/state.json", nil) { t.Error("未开启 include_data 时 data/ 不应被允许") } }