仰望星辰工作室

clearlove2.1

clearlove2.1/ internal/plugin/embassy_app_test.go 7.0 KB · 220 行 原始文件
1package plugin
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "clearlove/internal/database"
11 "clearlove/internal/models"
12)
13
14// TestEmbassyPlugin 加载仓库 plugins/embassy 真实插件,验证「友校大使馆」完整业务流:
15// 后台登记大使 → 昵称识别 → 验证账密 → post_created 落认证 → 卡片徽章。
16func TestEmbassyPlugin(t *testing.T) {
17 dataDir := setupAppEnv(t)
18
19 // 拷贝真实插件文件到临时插件目录
20 src, err := filepath.Abs(filepath.Join("..", "..", "plugins", "embassy"))
21 if err != nil {
22 t.Fatal(err)
23 }
24 dst := filepath.Join(dataDir, "plugins", "embassy")
25 if err := os.MkdirAll(dst, 0o755); err != nil {
26 t.Fatal(err)
27 }
28 for _, f := range []string{"plugin.json", "main.js"} {
29 b, err := os.ReadFile(filepath.Join(src, f))
30 if err != nil {
31 t.Fatalf("读取插件文件失败: %v", err)
32 }
33 if err := os.WriteFile(filepath.Join(dst, f), b, 0o644); err != nil {
34 t.Fatal(err)
35 }
36 }
37
38 SetEnabled("友校大使馆", true)
39 if err := LoadApp("embassy"); err != nil {
40 t.Fatalf("加载 embassy 插件失败: %v", err)
41 }
42 defer func() {
43 UnloadApp("embassy")
44 SetEnabled("友校大使馆", false)
45 }()
46
47 app := GetApp("embassy")
48 if app == nil {
49 t.Fatal("embassy 未登记到注册表")
50 }
51 if len(app.Routes) != 5 {
52 t.Fatalf("路由数 = %d,期望 5", len(app.Routes))
53 }
54 if len(app.Pages) != 1 || len(AdminMenus()) != 1 {
55 t.Fatalf("后台页/菜单注册异常")
56 }
57 if len(EventHooks("post_created")) != 1 {
58 t.Fatal("post_created 事件未注册")
59 }
60 if len(FilterHooks("filter.card")) != 1 {
61 t.Fatal("filter.card 未注册")
62 }
63
64 // 表已创建
65 if _, err := database.DB.Exec("SELECT COUNT(1) FROM pl_embassy_ambassador"); err != nil {
66 t.Fatalf("插件表未创建: %v", err)
67 }
68
69 // 简易 ctx 构造器(对齐 buildRequestCtx 的关键字段)
70 mkCtx := func(query, form map[string]string) map[string]any {
71 return map[string]any{
72 "req": map[string]any{"query": query, "form": form, "json": nil, "ip": "1.2.3.4", "fingerprint": "fp-test"},
73 "params": map[string]string{},
74 "userId": int64(0), "adminId": int64(1), "isAdmin": true, "csrf": "test-csrf",
75 }
76 }
77 call := func(fn string, arg any) (any, error) {
78 return app.Worker.Do(context.Background(), func(rt *jsRuntime) (any, error) {
79 return rt.callFn(fn, arg)
80 })
81 }
82 // 路由函数返回 {status?, json: {...}},直接调用时需解包
83 jbody := func(v any) map[string]any {
84 m, _ := v.(map[string]any)
85 if m == nil {
86 return nil
87 }
88 j, _ := m["json"].(map[string]any)
89 return j
90 }
91
92 // 1) 未登记昵称 → 不受保护
93 v, err := call("apiLookup", mkCtx(map[string]string{"nickname": "小美"}, nil))
94 if err != nil {
95 t.Fatalf("apiLookup 失败: %v", err)
96 }
97 if m := jbody(v); m == nil || m["protected"] == true {
98 t.Fatalf("未登记昵称不应受保护: %v", v)
99 }
100
101 // 2) 后台登记大使(密码 bcrypt 落库)
102 v, err = call("adminAdd", mkCtx(nil, map[string]string{
103 "school": "邻校一中", "account": "amb01", "password": "1234", "nickname": "邻校大使小美"}))
104 if err != nil {
105 t.Fatalf("adminAdd 失败: %v", err)
106 }
107 if m, _ := v.(map[string]any); m == nil || m["redirect"] == "" {
108 t.Fatalf("adminAdd 应重定向回管理页: %v", v)
109 }
110 var ambID int64
111 var hash, school string
112 if err := database.DB.QueryRow(
113 "SELECT id, school, password FROM pl_embassy_ambassador WHERE nickname='邻校大使小美'").
114 Scan(&ambID, &school, &hash); err != nil {
115 t.Fatalf("大使未入库: %v", err)
116 }
117 if school != "邻校一中" {
118 t.Fatalf("school 异常: %q", school)
119 }
120 if strings.EqualFold(hash, "1234") || len(hash) < 20 {
121 t.Fatalf("密码应为 bcrypt 哈希,实际: %q", hash)
122 }
123
124 // 3) 已登记昵称 → 受保护
125 v, err = call("apiLookup", mkCtx(map[string]string{"nickname": "邻校大使小美"}, nil))
126 if err != nil {
127 t.Fatalf("apiLookup 失败: %v", err)
128 }
129 if m := jbody(v); m == nil || m["protected"] != true || m["school"] != "邻校一中" {
130 t.Fatalf("已登记昵称应受保护: %v", v)
131 }
132
133 // 4) 错误密码 → 验证失败
134 v, err = call("apiVerify", mkCtx(nil, map[string]string{
135 "nickname": "邻校大使小美", "account": "amb01", "password": "wrong"}))
136 if err != nil {
137 t.Fatalf("apiVerify 失败: %v", err)
138 }
139 if m := jbody(v); m == nil {
140 t.Fatalf("apiVerify 返回异常: %v", v)
141 } else if ok, _ := m["ok"].(bool); ok {
142 t.Fatalf("错误密码不应通过验证: %v", v)
143 }
144
145 // 5) 正确密码 → 验证通过,验证记录落库
146 v, err = call("apiVerify", mkCtx(nil, map[string]string{
147 "nickname": "邻校大使小美", "account": "amb01", "password": "1234"}))
148 if err != nil {
149 t.Fatalf("apiVerify 失败: %v", err)
150 }
151 if m := jbody(v); m == nil {
152 t.Fatalf("apiVerify 返回异常: %v", v)
153 } else if ok, _ := m["ok"].(bool); !ok {
154 t.Fatalf("正确密码应通过验证: %v", v)
155 }
156 if models.QueryInt("SELECT COUNT(1) FROM pl_embassy_verify WHERE fingerprint='fp-test'") != 1 {
157 t.Fatal("验证记录未落库")
158 }
159
160 // 6) status:已验证指纹 → verified
161 v, err = call("apiStatus", mkCtx(map[string]string{"nickname": "邻校大使小美"}, nil))
162 if err != nil {
163 t.Fatalf("apiStatus 失败: %v", err)
164 }
165 if m := jbody(v); m == nil {
166 t.Fatalf("apiStatus 返回异常: %v", v)
167 } else if verified, _ := m["verified"].(bool); !verified {
168 t.Fatalf("status 应为已验证: %v", v)
169 }
170
171 // 7) post_created 处理(同步调用 handler)→ 认证落库
172 if _, err := call("onPostCreated", map[string]any{
173 "post_id": 101, "nickname": "邻校大使小美", "content": "你好"}); err != nil {
174 t.Fatalf("onPostCreated 失败: %v", err)
175 }
176 if models.QueryInt("SELECT COUNT(1) FROM pl_embassy_cert WHERE post_id=101") != 1 {
177 t.Fatal("认证记录未落库")
178 }
179
180 // 8) filter.card → 徽章追加
181 card := map[string]any{"id": int64(101), "badges": []any{}}
182 out := ApplyFilterValue("filter.card", card, nil)
183 om, _ := out.(map[string]any)
184 if om == nil {
185 t.Fatalf("filter.card 无返回: %v", out)
186 }
187 badges, _ := om["badges"].([]any)
188 found := false
189 for _, b := range badges {
190 if s, _ := b.(string); s == "邻校一中大使官方认证" {
191 found = true
192 }
193 }
194 if !found {
195 t.Fatalf("徽章未追加: %v", om["badges"])
196 }
197
198 // 9) 无验证时同昵称发帖 → 不落认证
199 if _, err := database.DB.Exec("DELETE FROM pl_embassy_verify"); err != nil {
200 t.Fatal(err)
201 }
202 if _, err := call("onPostCreated", map[string]any{
203 "post_id": 102, "nickname": "邻校大使小美", "content": "仿冒"}); err != nil {
204 t.Fatalf("onPostCreated 失败: %v", err)
205 }
206 if models.QueryInt("SELECT COUNT(1) FROM pl_embassy_cert WHERE post_id=102") != 0 {
207 t.Fatal("无有效验证不应落认证")
208 }
209
210 // 10) 删除大使 → 连带清理验证记录
211 if _, err := call("adminDelete", mkCtx(nil, map[string]string{"id": "1"})); err != nil {
212 t.Fatalf("adminDelete 失败: %v", err)
213 }
214 if models.QueryInt("SELECT COUNT(1) FROM pl_embassy_ambassador") != 0 {
215 t.Fatal("大使未删除")
216 }
217 if models.QueryInt("SELECT COUNT(1) FROM pl_embassy_verify") != 0 {
218 t.Fatal("验证记录未连带清理")
219 }
220}