test: smoke coverage for search, private messages, webhook/SSO
The fake smoke Discourse now serves /search.json (title substring matches over created topics), accepts private-message creation on POST /posts.json (targets comma-joined on the wire, deliveries recorded and exposed via /__pms.json), and the smoke script drives the real CLI through it: search hits, PM send + recipient assertion, webhook verify with a python-computed HMAC (good sig exits 0, bad sig exits 2 with the typed message), and sso verify decoding a signed Discourse Connect payload (tamper case exits 2). No live forum is contacted. Part of Redmine 507 (Discourse Go client).
This commit is contained in:
@@ -26,6 +26,7 @@ type server struct {
|
||||
nextCat int
|
||||
nextPost int
|
||||
nextT int
|
||||
pms []map[string]any // recorded private messages, creation order
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -88,6 +89,23 @@ func main() {
|
||||
s.nextPost++
|
||||
if topicID == 0 {
|
||||
title, _ := req["title"].(string)
|
||||
if tu, _ := req["target_usernames"].(string); tu != "" || hasAnyKey(req, "target_group_names", "target_emails") {
|
||||
// private-message creation
|
||||
if strings.Contains(str(req["target_usernames"]), "denied") {
|
||||
reply(w, 403, map[string]any{"errors": []string{"You are not permitted to view the requested resource."}})
|
||||
return
|
||||
}
|
||||
tid := s.nextT
|
||||
s.nextT++
|
||||
slug := strings.ToLower(strings.ReplaceAll(title, " ", "-"))
|
||||
s.pms = append(s.pms, map[string]any{
|
||||
"topic_id": tid, "title": title,
|
||||
"target_usernames": req["target_usernames"], "raw": req["raw"],
|
||||
})
|
||||
s.posts[pid] = map[string]any{"id": pid, "topic_id": tid, "raw": req["raw"], "post_number": 1}
|
||||
reply(w, 200, map[string]any{"id": pid, "topic_id": tid, "topic_slug": slug})
|
||||
return
|
||||
}
|
||||
catID, _ := req["category"].(float64)
|
||||
tid := s.nextT
|
||||
s.nextT++
|
||||
@@ -127,6 +145,24 @@ func main() {
|
||||
case path == "/latest.json" && r.Method == "GET":
|
||||
reply(w, 200, map[string]any{"topic_list": map[string]any{"topics": s.topics}})
|
||||
|
||||
case path == "/search.json" && r.Method == "GET":
|
||||
term := strings.ToLower(r.URL.Query().Get("term"))
|
||||
var topics, posts []map[string]any
|
||||
for _, t := range s.topics {
|
||||
if term != "" && strings.Contains(strings.ToLower(str(t["title"])), term) {
|
||||
topics = append(topics, t)
|
||||
posts = append(posts, map[string]any{
|
||||
"id": 900 + t["id"].(int), "topic_id": t["id"], "username": "smoker",
|
||||
"blurb": "match on " + str(t["title"]),
|
||||
"topic_title_headline": t["title"],
|
||||
})
|
||||
}
|
||||
}
|
||||
reply(w, 200, map[string]any{"posts": posts, "topics": topics, "users": []any{}, "categories": []any{}})
|
||||
|
||||
case path == "/__pms.json" && r.Method == "GET":
|
||||
reply(w, 200, map[string]any{"private_messages": s.pms})
|
||||
|
||||
default:
|
||||
reply(w, 404, map[string]any{"errors": []string{"not found on the smoke instance"}})
|
||||
}
|
||||
@@ -147,3 +183,19 @@ func orDefault(v any, def string) any {
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// str renders any as a display string (missing keys -> "").
|
||||
func str(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
// hasAnyKey reports whether any of the keys holds a non-empty string.
|
||||
func hasAnyKey(m map[string]any, keys ...string) bool {
|
||||
for _, k := range keys {
|
||||
if str(m[k]) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user