| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- // Command fakefcmd is a test double for the FCM HTTP v1 API. It
- // accepts POST /v1/projects/.../messages:send and acks 200 with a
- // fake message name. Used by M1 to prove the round-trip without
- // burning FCM credits or requiring a Google service account.
- //
- // In M3 we replace this with a real FCM client in internal/fcm/.
- //
- // Run:
- //
- // fakefcmd --addr :8820
- //
- // The deliverd-fcm worker points at this via env var
- // BA_FAKECMD_URL (or just hardcoded for M1).
- package main
- import (
- "encoding/json"
- "flag"
- "fmt"
- "io"
- "log/slog"
- "net/http"
- "os"
- "sync/atomic"
- "time"
- )
- func main() {
- var (
- addr = flag.String("addr", ":8820", "HTTP listen addr")
- failRate = flag.Float64("fail-rate", 0, "fraction of requests to fail (0-1) for fault injection")
- )
- flag.Parse()
- logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
- var (
- received atomic.Uint64
- failed atomic.Uint64
- )
- mux := http.NewServeMux()
- mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(map[string]any{
- "status": "ok",
- "service": "fakefcmd",
- "received": received.Load(),
- "failed": failed.Load(),
- })
- })
- mux.HandleFunc("/v1/projects/", func(w http.ResponseWriter, r *http.Request) {
- received.Add(1)
- body, _ := io.ReadAll(r.Body)
- _ = r.Body.Close()
- if *failRate > 0 && (float64(received.Load())*1.0/100.0) < *failRate {
- // crude: 1 in N fails. good enough for fault-injection tests.
- failed.Add(1)
- logger.Warn("fakefcmd fault injection", "path", r.URL.Path)
- w.WriteHeader(http.StatusServiceUnavailable)
- _, _ = w.Write([]byte(`{"error":{"status":"UNAVAILABLE","message":"fakefcmd fault inject"}}`))
- return
- }
- // Log a compact view (don't print full payload; can be fat).
- var msg struct {
- Message struct {
- Token string `json:"token"`
- Notification struct{ Title, Body string } `json:"notification"`
- Data map[string]string `json:"data"`
- } `json:"message"`
- }
- _ = json.Unmarshal(body, &msg)
- logger.Info("fakefcmd send",
- "path", r.URL.Path,
- "token", truncate(msg.Message.Token, 32),
- "title", truncate(msg.Message.Notification.Title, 80),
- "data_keys", len(msg.Message.Data),
- "alert_id", msg.Message.Data["alert_id"],
- )
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(map[string]string{
- "name": fmt.Sprintf("projects/fakefcmd/messages/%d", time.Now().UnixNano()),
- })
- })
- logger.Info("fakefcmd listening", "addr", *addr)
- srv := &http.Server{
- Addr: *addr,
- Handler: mux,
- ReadHeaderTimeout: 5 * time.Second,
- }
- if err := srv.ListenAndServe(); err != nil {
- logger.Error("fakefcmd", "err", err)
- os.Exit(1)
- }
- }
- func truncate(s string, n int) string {
- if len(s) <= n {
- return s
- }
- return s[:n] + "…"
- }
|