From 20a9589eb8d8f2a613af56a20635363320eca5b0 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:05:02 -0600 Subject: [PATCH] add HTTP server test with main and testdata --- main.go | 57 +++++++++++++++++++--- main_test.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 main_test.go diff --git a/main.go b/main.go index e475b36..a1bc241 100644 --- a/main.go +++ b/main.go @@ -12,8 +12,10 @@ import ( "net/http" "os/signal" "slices" + "strconv" "strings" "syscall" + "time" ) func main() { @@ -63,7 +65,8 @@ func newHandler(cfg Config) http.HandlerFunc { mux := http.NewServeMux() mux.Handle("POST /api/v1/events/slack", http.HandlerFunc(newHandlerPostAPIV1EventsSlack(cfg))) - mux.Handle("GET /api/v1/messages", http.HandlerFunc(newHandlerGetAPIV1Message(cfg))) + mux.Handle("GET /api/v1/threads", http.HandlerFunc(newHandlerGetAPIV1Threads(cfg))) + mux.Handle("GET /api/v1/threads/{thread}", http.HandlerFunc(newHandlerGetAPIV1ThreadsThread(cfg))) return func(w http.ResponseWriter, r *http.Request) { if cfg.Debug { @@ -76,17 +79,57 @@ func newHandler(cfg Config) http.HandlerFunc { } } -func newHandlerGetAPIV1Message(cfg Config) http.HandlerFunc { +func newHandlerGetAPIV1Threads(cfg Config) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if u, p, _ := r.BasicAuth(); u != cfg.BasicAuthUser || p != cfg.BasicAuthPassword { - http.Error(w, "shoo", http.StatusForbidden) + if !basicAuth(cfg, w, r) { return } - http.Error(w, "not impl", http.StatusNotImplemented) + since := time.Unix(0, 0) + if sinceS := r.URL.Query().Get("since"); sinceS == "" { + } else if n, err := strconv.ParseInt(sinceS, 10, 64); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } else { + since = time.Unix(n, 0) + } + + threads, err := cfg.storage.ThreadsSince(r.Context(), since) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(map[string]any{"threads": threads}) } } +func newHandlerGetAPIV1ThreadsThread(cfg Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !basicAuth(cfg, w, r) { + return + } + + thread := strings.Split(strings.Split(r.URL.Path, "/threads/")[1], "/")[0] + + messages, err := cfg.storage.Thread(r.Context(), thread) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(map[string]any{"thread": map[string]any{"messages": messages}}) + } +} + +func basicAuth(cfg Config, w http.ResponseWriter, r *http.Request) bool { + if u, p, _ := r.BasicAuth(); u != cfg.BasicAuthUser || p != cfg.BasicAuthPassword { + http.Error(w, "shoo", http.StatusForbidden) + return false + } + return true +} + func newHandlerPostAPIV1EventsSlack(cfg Config) http.HandlerFunc { if cfg.InitializeSlack { return handlerPostAPIV1EventsSlackInitialize @@ -126,7 +169,7 @@ func _newHandlerPostAPIV1EventsSlack(cfg Config) http.HandlerFunc { } else if allowList.Token != cfg.SlackToken { http.Error(w, "invalid .token", http.StatusForbidden) return - } else if !slices.Contains(strings.Split(cfg.SlackChannels, ","), allowList.Event.Channel) { + } else if !slices.Contains(cfg.SlackChannels, allowList.Event.Channel) { return } @@ -143,6 +186,6 @@ func _newHandlerPostAPIV1EventsSlack(cfg Config) http.HandlerFunc { http.Error(w, err.Error(), http.StatusInternalServerError) return } - log.Printf("ingested %+v", m) + log.Printf("ingested %v", m.ID) } } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..deb495d --- /dev/null +++ b/main_test.go @@ -0,0 +1,130 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "slices" + "strconv" + "strings" + "testing" + "time" +) + +func TestRun(t *testing.T) { + ctx, can := context.WithTimeout(context.Background(), time.Second*10) + defer can() + + port := func() int { + s := httptest.NewServer(http.HandlerFunc(http.NotFound)) + s.Close() + u, err := url.Parse(s.URL) + if err != nil { + t.Fatal(err) + } + portS := strings.Split(u.Host, ":")[1] + port, err := strconv.ParseInt(portS, 10, 32) + if err != nil { + t.Fatal(err) + } + return int(port) + }() + u := fmt.Sprintf("http://localhost:%d", port) + + cfg := Config{} + cfg.Port = port + cfg.driver = NewRAM() + cfg.storage = NewStorage(cfg.driver) + cfg.queue = NewQueue(cfg.driver) + cfg.SlackToken = "redacted" + cfg.SlackChannels = []string{"C06U1DDBBU4"} + + go func() { + if err := run(ctx, cfg); err != nil && ctx.Err() == nil { + t.Fatal(err) + } + }() + + for { + if resp, err := http.Get(u); err == nil { + resp.Body.Close() + break + } + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case <-time.After(time.Millisecond * 50): + } + } + + t.Run("POST /api/v1/events/slack", func(t *testing.T) { + b, err := os.ReadFile(path.Join("testdata", "slack_events", "opsgenie_alert_3.json")) + if err != nil { + t.Fatal(err) + } + resp, err := http.Post(fmt.Sprintf("%s/api/v1/events/slack", u), "application/json", bytes.NewReader(b)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(resp.Body) + t.Fatalf("(%d) %s", resp.StatusCode, b) + } + }) + + t.Run("GET /api/v1/threads", func(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("%s/api/v1/threads", u)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(resp.Body) + t.Fatalf("(%d) %s", resp.StatusCode, b) + } + var result struct { + Threads []string + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + t.Fatal(err) + } else if !slices.Contains(result.Threads, "1712911957.023359") { + t.Fatal(result.Threads) + } + }) + + t.Run("GET /api/v1/threads/1712911957.023359", func(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("%s/api/v1/threads/1712911957.023359", u)) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(resp.Body) + t.Fatalf("(%d) %s", resp.StatusCode, b) + } + + var result struct { + Thread struct { + Messages []Message + } + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + t.Fatal(err) + } else if len(result.Thread.Messages) != 1 { + t.Fatal(result.Thread) + } else { + t.Logf("%+v", result.Thread.Messages[0]) + } + }) +}