Skip to content

test: add unit tests for observability modules#53

Merged
nilushancosta merged 1 commit intoopenchoreo:mainfrom
nilushancosta:observability
Mar 18, 2026
Merged

test: add unit tests for observability modules#53
nilushancosta merged 1 commit intoopenchoreo:mainfrom
nilushancosta:observability

Conversation

@nilushancosta
Copy link
Copy Markdown
Contributor

@nilushancosta nilushancosta commented Mar 18, 2026

Purpose

Add more unit tests for observability modules
openchoreo/openchoreo#2755

Goals

Increase test coverage

Approach

Added Go unit tests for observability-logs-openobserve, observability-metrics-prometheus and observability-tracing-openobserve modules

Summary by CodeRabbit

  • Bug Fixes

    • Update alert rule now returns a 400 Bad Request (with "alert rule not found") when the target rule is missing instead of a 500 error.
  • Tests

    • Added broad tests covering alert rule create/update/delete success and error paths.
    • Added tests for context cancellation, non-success HTTP responses, malformed responses, invalid inputs, and debug logging for query generation.
    • Expanded webhook and forwarding validation to ensure correct payloads and error handling.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Adds extensive unit tests across logs, metrics, and tracing modules and adjusts UpdateAlertRule error handling to return 400 BadRequest when the client error contains "not found". No public API signatures were changed.

Changes

Cohort / File(s) Summary
Logs — Handler Tests
observability-logs-openobserve/internal/handlers_test.go
Many new tests covering UpdateAlertRule (success, not-found, invalid, generic errors), Create/Get/Delete alert rules, QueryLogs (workflow/component scopes), webhook handling, and numerous helper conversion utilities.
Logs — OpenObserve Client Tests
observability-logs-openobserve/internal/openobserve/client_test.go
New tests for Create/Update/Delete/Get alerts and ExecuteSearchQuery covering lookup failures, HTTP errors, config errors, missing response IDs, connection errors, non-OK statuses, and malformed JSON.
Logs — Observer Client Tests
observability-logs-openobserve/internal/observer/client_test.go
Tests for ForwardAlert handling canceled contexts and non-2xx responses.
Logs — Handler Change
observability-logs-openobserve/internal/handlers.go
UpdateAlertRule now maps client errors containing "not found" to a 400 BadRequest response with message "alert rule not found" (instead of previously returning 500).
Metrics — Config & Handler Tests
observability-metrics-prometheus/internal/config_test.go, observability-metrics-prometheus/internal/handlers_test.go
Tests for LoadConfig fallback log level and edge-case parsing in extractAlertFields (whitespace, negative/zero values).
Metrics — Observer Client Tests
observability-metrics-prometheus/internal/observer/client_test.go
ForwardAlert tests for context cancellation, non-success status codes, and empty response body.
Tracing — Handler & Client Tests
observability-tracing-openobserve/internal/handlers_test.go, observability-tracing-openobserve/internal/openobserve/client_test.go
Tests for QuerySpansForTrace server error handling, ExecuteSearchQuery invalid JSON, and invalid stream identifier handling.
Tracing — Query Tests / Debug Logging
observability-tracing-openobserve/internal/openobserve/queries_test.go
Adds stdout capture and debug-logger tests verifying generated SQL strings appear in debug output for traces/span queries.
Manifest
go.mod
Updated module manifest (test-related changes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • LakshanSS
  • akila-i

Poem

🐰 In code and test the carrots hide,
I hop through failures, far and wide.
Alerts and traces, logs that sing—
I forward, parse, and lightly spring,
Hooray for tests that make things right! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding unit tests across multiple observability modules with 602+ new test lines.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
observability-logs-openobserve/internal/openobserve/client_test.go (1)

425-439: Tighten mock handlers to reject unexpected routes/methods.

These handlers currently allow some unexpected requests (broad method match and no default branch), which can let tests pass even when request construction regresses.

Example hardening pattern (apply similarly in the three tests)
 func TestUpdateAlert_HTTPError(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		switch {
 		case r.Method == "GET" && r.URL.Path == "/api/v2/default/alerts":
 			resp := map[string]interface{}{
 				"list": []map[string]string{
 					{"alert_id": "alert-upd-err", "name": "my-alert"},
 				},
 			}
 			json.NewEncoder(w).Encode(resp)
-		case r.Method == "PUT":
+		case r.Method == "PUT" && r.URL.Path == "/api/v2/default/alerts/alert-upd-err":
 			w.WriteHeader(http.StatusBadRequest)
 			w.Write([]byte("bad request"))
+		default:
+			t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
+			w.WriteHeader(http.StatusNotFound)
 		}
 	}))

Also applies to: 480-494, 599-613

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observability-logs-openobserve/internal/openobserve/client_test.go` around
lines 425 - 439, TestUpdateAlert_HTTPError's httptest handler is too permissive
(broad r.Method == "PUT" case and no default) so unexpected requests can sneak
through; update the handler in TestUpdateAlert_HTTPError to match both method
and exact path for the PUT branch (e.g., r.Method == "PUT" && r.URL.Path ==
"/api/v2/default/alerts/{id}" or the concrete path your client should call) and
add a default branch that calls t.Fatalf or writes http.StatusNotFound for any
unmatched method/path to fail the test; apply the same tightening pattern to the
other two mock handlers referenced (the tests covering lines 480-494 and
599-613) so all handlers explicitly validate method+path and reject unexpected
requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@observability-logs-openobserve/internal/handlers_test.go`:
- Around line 792-833: This test currently asserts a generic 500 for a missing
rule; update TestUpdateAlertRule_NotFound to assert a more specific response
(e.g., expect gen.UpdateAlertRule404JSONResponse or an upstream 400 response
when the upstream returns 400) instead of gen.UpdateAlertRule500JSONResponse, by
adjusting the assertion that checks the return type from
handler.UpdateAlertRule; do the same for the related tests (the other cases
around TestGetAlertRule_NotFound and TestUpdateAlertRule_InvalidError
referenced) so empty lookup results map to a 404 response and upstream 400
responses are preserved as 400s rather than normalized to 500. Ensure you
reference the handler.UpdateAlertRule call and the response type checks when
making the change.

In `@observability-tracing-openobserve/internal/openobserve/queries_test.go`:
- Around line 450-534: The tests currently call debugLogger() which writes noisy
output to stderr and never verifies that a debug log was actually emitted;
change each test (TestGenerateTracesListQuery_DebugLogging,
TestGenerateSpansListQuery_DebugLogging,
TestGenerateSpanDetailQuery_DebugLogging) to create a bytes.Buffer, construct an
slog.TextHandler that writes to that buffer (e.g., slog.NewTextHandler(&buf,
&slog.HandlerOptions{Level: slog.LevelDebug})), build a logger with
slog.New(handler), pass that logger into
generateTracesListQuery/generateSpansListQuery/generateSpanDetailQuery, and
after the call assert that buf.String() contains the expected debug output (for
example the SQL snippet you already check like "FROM mystream" or "trace_id =
'abc123'") so the test verifies a debug record was emitted instead of printing
to stderr.

---

Nitpick comments:
In `@observability-logs-openobserve/internal/openobserve/client_test.go`:
- Around line 425-439: TestUpdateAlert_HTTPError's httptest handler is too
permissive (broad r.Method == "PUT" case and no default) so unexpected requests
can sneak through; update the handler in TestUpdateAlert_HTTPError to match both
method and exact path for the PUT branch (e.g., r.Method == "PUT" && r.URL.Path
== "/api/v2/default/alerts/{id}" or the concrete path your client should call)
and add a default branch that calls t.Fatalf or writes http.StatusNotFound for
any unmatched method/path to fail the test; apply the same tightening pattern to
the other two mock handlers referenced (the tests covering lines 480-494 and
599-613) so all handlers explicitly validate method+path and reject unexpected
requests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1bfec8f-17d6-49c0-baa9-93bf5db5655e

📥 Commits

Reviewing files that changed from the base of the PR and between 5233050 and 03380c9.

📒 Files selected for processing (9)
  • observability-logs-openobserve/internal/handlers_test.go
  • observability-logs-openobserve/internal/observer/client_test.go
  • observability-logs-openobserve/internal/openobserve/client_test.go
  • observability-metrics-prometheus/internal/config_test.go
  • observability-metrics-prometheus/internal/handlers_test.go
  • observability-metrics-prometheus/internal/observer/client_test.go
  • observability-tracing-openobserve/internal/handlers_test.go
  • observability-tracing-openobserve/internal/openobserve/client_test.go
  • observability-tracing-openobserve/internal/openobserve/queries_test.go

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Signed-off-by: Nilushan Costa <nilushan@wso2.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
observability-logs-openobserve/internal/handlers_test.go (1)

1070-1092: ⚠️ Potential issue | 🟠 Major

These tests lock broad 500 mappings for client/upstream errors.

DeleteAlertRule not-found-like failures and CreateAlertRule upstream 400 are asserted as 500 here, which bakes generic internal-error semantics into the suite.

Also applies to: 1094-1147

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observability-logs-openobserve/internal/handlers_test.go` around lines 1070 -
1092, The tests currently assert that upstream "not found" and client 400 errors
surface as generic 500 responses; update the test cases for DeleteAlertRule and
CreateAlertRule to simulate appropriate upstream status codes and assert the
corresponding specific handler response types (e.g., replace expecting
gen.DeleteAlertRule500JSONResponse with the more specific
gen.DeleteAlertRule404JSONResponse or the proper 4xx response type for
CreateAlertRule). Locate the tests around TestDeleteAlertRule_Error and the
CreateAlertRule tests that use NewLogsHandler and openobserve.NewClient, change
the httptest server responses to return realistic status codes/body and update
the resp type assertions to match the handler's intended 4xx mappings instead of
forcing 500.
observability-logs-openobserve/internal/openobserve/client_test.go (1)

504-519: ⚠️ Potential issue | 🟠 Major

Make config-error test deterministic (avoid localhost transport dependency).

This test can pass for the wrong reason (connection failure) even if config validation regresses. Use an httptest.Server and assert no request is made when config generation fails.

Proposed deterministic test patch
 func TestCreateAlert_ConfigError(t *testing.T) {
-	client := newTestClient("http://localhost:1")
+	requestHit := make(chan struct{}, 1)
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		requestHit <- struct{}{}
+		w.WriteHeader(http.StatusCreated)
+		_, _ = w.Write([]byte(`{"id":"unexpected"}`))
+	}))
+	defer server.Close()
+
+	client := newTestClient(server.URL)
 	enabled := true
 	name := "test"
 	// Invalid operator causes config generation to fail
 	_, err := client.CreateAlert(context.Background(), LogAlertParams{
 		Name:     &name,
 		Operator: "invalid_op",
 		Window:   "5m",
 		Interval: "1m",
 		Enabled:  &enabled,
 	})
 	if err == nil {
 		t.Fatal("expected error for invalid config")
 	}
+	select {
+	case <-requestHit:
+		t.Fatal("expected config validation failure before any HTTP request")
+	default:
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observability-logs-openobserve/internal/openobserve/client_test.go` around
lines 504 - 519, The test TestCreateAlert_ConfigError currently uses
newTestClient("http://localhost:1") making it non-deterministic; replace that
with an httptest.Server whose handler fails the test if invoked, pass server.URL
into newTestClient, and defer server.Close(); then call client.CreateAlert with
the invalid LogAlertParams (Operator: "invalid_op") and assert err != nil — this
ensures config generation fails before any network request and the server
handler guarantees no request was made. Use the existing test name
TestCreateAlert_ConfigError and the CreateAlert/LogAlertParams symbols to locate
and update the test.
🧹 Nitpick comments (2)
observability-logs-openobserve/internal/handlers_test.go (1)

830-832: Strengthen the not-found update assertion to include payload fields.

This test currently checks only response type. Also assert Title == BadRequest and Message == "alert rule not found" so the handler contract change is fully protected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observability-logs-openobserve/internal/handlers_test.go` around lines 830 -
832, The test only checks the response type; strengthen it by casting resp to
gen.UpdateAlertRule400JSONResponse, extracting resp.Payload and asserting
resp.Payload.Title == "BadRequest" and resp.Payload.Message == "alert rule not
found" (using t.Fatalf or t.Errorf on mismatch) so the handler contract for
UpdateAlertRule400JSONResponse is fully verified; locate the assertion near the
existing type check for resp and add the payload field comparisons.
observability-logs-openobserve/internal/handlers.go (1)

267-271: Avoid coupling not-found mapping to error message text.

This branch depends on strings.Contains(err.Error(), "not found"), which is brittle across wrapper/message changes in the OpenObserve client. Prefer a typed/sentinel error and errors.Is(...) for stable status mapping.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observability-logs-openobserve/internal/handlers.go` around lines 267 - 271,
Replace the brittle string check on err.Error() with a typed error comparison
using errors.Is to detect the not-found case: import "errors" and use
errors.Is(err, <client>.ErrNotFound) (or the OpenObserve client's
sentinel/not-found error value or helper, e.g., openobserve.ErrNotFound or
openobserve.IsNotFound(err)) in the branch that currently returns
gen.UpdateAlertRule400JSONResponse so the same response is returned when
errors.Is(...) is true; retain the existing returned
gen.UpdateAlertRule400JSONResponse structure and fall back to the existing error
handling for other errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@observability-logs-openobserve/internal/handlers_test.go`:
- Around line 1070-1092: The tests currently assert that upstream "not found"
and client 400 errors surface as generic 500 responses; update the test cases
for DeleteAlertRule and CreateAlertRule to simulate appropriate upstream status
codes and assert the corresponding specific handler response types (e.g.,
replace expecting gen.DeleteAlertRule500JSONResponse with the more specific
gen.DeleteAlertRule404JSONResponse or the proper 4xx response type for
CreateAlertRule). Locate the tests around TestDeleteAlertRule_Error and the
CreateAlertRule tests that use NewLogsHandler and openobserve.NewClient, change
the httptest server responses to return realistic status codes/body and update
the resp type assertions to match the handler's intended 4xx mappings instead of
forcing 500.

In `@observability-logs-openobserve/internal/openobserve/client_test.go`:
- Around line 504-519: The test TestCreateAlert_ConfigError currently uses
newTestClient("http://localhost:1") making it non-deterministic; replace that
with an httptest.Server whose handler fails the test if invoked, pass server.URL
into newTestClient, and defer server.Close(); then call client.CreateAlert with
the invalid LogAlertParams (Operator: "invalid_op") and assert err != nil — this
ensures config generation fails before any network request and the server
handler guarantees no request was made. Use the existing test name
TestCreateAlert_ConfigError and the CreateAlert/LogAlertParams symbols to locate
and update the test.

---

Nitpick comments:
In `@observability-logs-openobserve/internal/handlers_test.go`:
- Around line 830-832: The test only checks the response type; strengthen it by
casting resp to gen.UpdateAlertRule400JSONResponse, extracting resp.Payload and
asserting resp.Payload.Title == "BadRequest" and resp.Payload.Message == "alert
rule not found" (using t.Fatalf or t.Errorf on mismatch) so the handler contract
for UpdateAlertRule400JSONResponse is fully verified; locate the assertion near
the existing type check for resp and add the payload field comparisons.

In `@observability-logs-openobserve/internal/handlers.go`:
- Around line 267-271: Replace the brittle string check on err.Error() with a
typed error comparison using errors.Is to detect the not-found case: import
"errors" and use errors.Is(err, <client>.ErrNotFound) (or the OpenObserve
client's sentinel/not-found error value or helper, e.g., openobserve.ErrNotFound
or openobserve.IsNotFound(err)) in the branch that currently returns
gen.UpdateAlertRule400JSONResponse so the same response is returned when
errors.Is(...) is true; retain the existing returned
gen.UpdateAlertRule400JSONResponse structure and fall back to the existing error
handling for other errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff90f6dc-3ae0-4e2a-b573-5b9ec75814a7

📥 Commits

Reviewing files that changed from the base of the PR and between 03380c9 and 5b875fa.

📒 Files selected for processing (10)
  • observability-logs-openobserve/internal/handlers.go
  • observability-logs-openobserve/internal/handlers_test.go
  • observability-logs-openobserve/internal/observer/client_test.go
  • observability-logs-openobserve/internal/openobserve/client_test.go
  • observability-metrics-prometheus/internal/config_test.go
  • observability-metrics-prometheus/internal/handlers_test.go
  • observability-metrics-prometheus/internal/observer/client_test.go
  • observability-tracing-openobserve/internal/handlers_test.go
  • observability-tracing-openobserve/internal/openobserve/client_test.go
  • observability-tracing-openobserve/internal/openobserve/queries_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • observability-tracing-openobserve/internal/openobserve/queries_test.go
  • observability-metrics-prometheus/internal/observer/client_test.go
  • observability-tracing-openobserve/internal/openobserve/client_test.go
  • observability-metrics-prometheus/internal/handlers_test.go

@nilushancosta nilushancosta merged commit f66fa02 into openchoreo:main Mar 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants