Skip to content

Commit 2aa6079

Browse files
committed
chore(tests): add e2e tests for tracing
1 parent 9037689 commit 2aa6079

File tree

6 files changed

+393
-0
lines changed

6 files changed

+393
-0
lines changed

test_integrations/phoenix_app/config/config.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ config :opentelemetry, span_processor: {Sentry.OpenTelemetry.SpanProcessor, []}
6565
config :opentelemetry,
6666
sampler: {Sentry.OpenTelemetry.Sampler, [drop: ["Elixir.Oban.Stager process"]]}
6767

68+
# Configure OpenTelemetry to use Sentry propagator for distributed tracing
69+
config :opentelemetry,
70+
text_map_propagators: [
71+
:trace_context,
72+
:baggage,
73+
Sentry.OpenTelemetry.Propagator
74+
]
75+
6876
# Import environment specific config. This must remain at the bottom
6977
# of this file so it overrides the configuration defined above.
7078
import_config "#{config_env()}.exs"

test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,35 @@ defmodule PhoenixAppWeb.PageController do
4848

4949
render(conn, :home, layout: false)
5050
end
51+
52+
# E2E tracing test endpoints
53+
54+
def api_error(_conn, _params) do
55+
raise ArithmeticError, "bad argument in arithmetic expression"
56+
end
57+
58+
def health(conn, _params) do
59+
json(conn, %{status: "ok"})
60+
end
61+
62+
def api_data(conn, _params) do
63+
Tracer.with_span "fetch_data" do
64+
users = Repo.all(User)
65+
66+
Tracer.with_span "process_data" do
67+
user_count = length(users)
68+
69+
first_user = Repo.get(User, 1)
70+
71+
json(conn, %{
72+
message: "Data fetched successfully",
73+
data: %{
74+
user_count: user_count,
75+
first_user: if(first_user, do: first_user.name, else: nil),
76+
timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
77+
}
78+
})
79+
end
80+
end
81+
end
5182
end

test_integrations/phoenix_app/lib/phoenix_app_web/endpoint.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ defmodule PhoenixAppWeb.Endpoint do
4444
plug Plug.RequestId
4545
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
4646

47+
plug :cors
48+
4749
plug Plug.Parsers,
4850
parsers: [:urlencoded, :multipart, :json],
4951
pass: ["*/*"],
@@ -53,4 +55,22 @@ defmodule PhoenixAppWeb.Endpoint do
5355
plug Plug.Head
5456
plug Plug.Session, @session_options
5557
plug PhoenixAppWeb.Router
58+
59+
# CORS plug for e2e tests - allows the Svelte frontend to make
60+
# cross-origin requests to the Phoenix backend
61+
defp cors(conn, _opts) do
62+
conn
63+
|> put_resp_header("access-control-allow-origin", "*")
64+
|> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
65+
|> put_resp_header("access-control-allow-headers", "content-type, sentry-trace, baggage")
66+
|> handle_preflight()
67+
end
68+
69+
defp handle_preflight(%{method: "OPTIONS"} = conn) do
70+
conn
71+
|> send_resp(200, "")
72+
|> halt()
73+
end
74+
75+
defp handle_preflight(conn), do: conn
5676
end

test_integrations/phoenix_app/lib/phoenix_app_web/router.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ defmodule PhoenixAppWeb.Router do
1212

1313
pipeline :api do
1414
plug :accepts, ["json"]
15+
plug :put_cors_headers
16+
end
17+
18+
defp put_cors_headers(conn, _opts) do
19+
conn
20+
|> put_resp_header("access-control-allow-origin", "*")
21+
|> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
22+
|> put_resp_header("access-control-allow-headers", "content-type, authorization, sentry-trace, baggage")
1523
end
1624

1725
scope "/", PhoenixAppWeb do
@@ -32,6 +40,15 @@ defmodule PhoenixAppWeb.Router do
3240
live "/users/:id/show/edit", UserLive.Show, :edit
3341
end
3442

43+
# For e2e DT tests with a front-end app
44+
scope "/", PhoenixAppWeb do
45+
pipe_through :api
46+
47+
get "/error", PageController, :api_error
48+
get "/health", PageController, :health
49+
get "/api/data", PageController, :api_data
50+
end
51+
3552
# Other scopes may use custom stacks.
3653
# scope "/api", PhoenixAppWeb do
3754
# pipe_through :api
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import fs from "fs";
2+
import path from "path";
3+
4+
const DEBUG_LOG_PATH = path.join(
5+
process.cwd(),
6+
"..",
7+
"phoenix_app",
8+
"tmp",
9+
"sentry_debug_events.log"
10+
);
11+
12+
export interface SentryEvent {
13+
type?: string;
14+
transaction?: string;
15+
exception?: Array<{
16+
type: string;
17+
value: string;
18+
}>;
19+
contexts?: {
20+
trace?: {
21+
trace_id?: string;
22+
span_id?: string;
23+
parent_span_id?: string;
24+
op?: string;
25+
data?: Record<string, any>;
26+
};
27+
};
28+
_meta?: {
29+
dsc?: {
30+
sample_rand?: string;
31+
};
32+
};
33+
request?: {
34+
headers?: Record<string, string>;
35+
};
36+
}
37+
38+
export interface SentryEnvelope {
39+
headers: {
40+
trace?: {
41+
trace_id?: string;
42+
sample_rate?: string;
43+
sample_rand?: string;
44+
sampled?: string;
45+
environment?: string;
46+
public_key?: string;
47+
[key: string]: any;
48+
};
49+
};
50+
items: SentryEvent[];
51+
}
52+
53+
export interface LoggedEvents {
54+
events: SentryEvent[];
55+
envelopes: SentryEnvelope[];
56+
event_count: number;
57+
}
58+
59+
export function getLoggedEvents(): LoggedEvents {
60+
const events: SentryEvent[] = [];
61+
const envelopes: SentryEnvelope[] = [];
62+
63+
if (!fs.existsSync(DEBUG_LOG_PATH)) {
64+
return { events: [], envelopes: [], event_count: 0 };
65+
}
66+
67+
const content = fs.readFileSync(DEBUG_LOG_PATH, "utf-8");
68+
const lines = content
69+
.trim()
70+
.split("\n")
71+
.filter((line) => line.trim());
72+
73+
for (const line of lines) {
74+
try {
75+
const data = JSON.parse(line);
76+
77+
// Check if it's an envelope format
78+
if (data.headers) {
79+
envelopes.push(data as SentryEnvelope);
80+
if (data.items) {
81+
events.push(...data.items);
82+
}
83+
} else {
84+
// Individual event
85+
events.push(data as SentryEvent);
86+
}
87+
} catch (e) {
88+
// Skip malformed lines
89+
}
90+
}
91+
92+
return { events, envelopes, event_count: events.length };
93+
}
94+
95+
export function clearLoggedEvents(): void {
96+
if (fs.existsSync(DEBUG_LOG_PATH)) {
97+
fs.writeFileSync(DEBUG_LOG_PATH, "");
98+
}
99+
}

0 commit comments

Comments
 (0)