Skip to content

Commit e578860

Browse files
test: secure image
1 parent 5f52526 commit e578860

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
3+
import { SecureImageComponent } from "./SecureImageComponent";
4+
5+
describe("SecureImageComponent", () => {
6+
beforeEach(() => {
7+
vi.clearAllMocks();
8+
});
9+
10+
describe("Default blocking behavior", () => {
11+
it("should block images by default and show warning message", () => {
12+
render(<SecureImageComponent src="https://example.com/image.jpg" />);
13+
14+
expect(screen.getByText(/Image blocked for security/)).toBeInTheDocument();
15+
expect(screen.getByText("Load Image")).toBeInTheDocument();
16+
expect(screen.queryByRole("img")).not.toBeInTheDocument();
17+
});
18+
19+
it("should display the image URL", () => {
20+
const testUrl = "https://example.com/test-image.png";
21+
render(<SecureImageComponent src={testUrl} />);
22+
23+
expect(screen.getByText(testUrl)).toBeInTheDocument();
24+
});
25+
26+
it("should handle invalid src prop", () => {
27+
render(<SecureImageComponent src={undefined} />);
28+
29+
expect(screen.getByText("[Invalid image: no source]")).toBeInTheDocument();
30+
expect(screen.queryByText("Load Image")).not.toBeInTheDocument();
31+
});
32+
});
33+
34+
describe("Query parameter detection", () => {
35+
it("should detect and display query parameters", () => {
36+
render(<SecureImageComponent src="https://example.com/image.jpg?user=123&token=abc" />);
37+
38+
expect(screen.getByText(/Warning: URL contains query parameters/)).toBeInTheDocument();
39+
expect(screen.getByText(/"user": "123"/)).toBeInTheDocument();
40+
expect(screen.getByText(/"token": "abc"/)).toBeInTheDocument();
41+
});
42+
43+
it("should not show query parameter warning for URLs without parameters", () => {
44+
render(<SecureImageComponent src="https://example.com/image.jpg" />);
45+
46+
expect(screen.queryByText(/Warning: URL contains query parameters/)).not.toBeInTheDocument();
47+
});
48+
49+
it("should handle relative URLs with query parameters", () => {
50+
render(<SecureImageComponent src="/images/test.jpg?id=456&session=xyz" />);
51+
52+
expect(screen.getByText(/Warning: URL contains query parameters/)).toBeInTheDocument();
53+
expect(screen.getByText(/"id": "456"/)).toBeInTheDocument();
54+
expect(screen.getByText(/"session": "xyz"/)).toBeInTheDocument();
55+
});
56+
57+
it("should handle malformed URLs gracefully", () => {
58+
render(<SecureImageComponent src="not-a-valid-url://image" />);
59+
60+
// Should still display the URL even if it can't be parsed
61+
expect(screen.getByText("not-a-valid-url://image")).toBeInTheDocument();
62+
expect(screen.getByText("Load Image")).toBeInTheDocument();
63+
});
64+
});
65+
66+
describe("User interaction", () => {
67+
it("should show image when Load Image button is clicked", () => {
68+
const testUrl = "https://example.com/image.jpg";
69+
render(<SecureImageComponent src={testUrl} alt="Test image" />);
70+
71+
// Initially no image
72+
expect(screen.queryByRole("img")).not.toBeInTheDocument();
73+
74+
// Click load button
75+
const loadButton = screen.getByText("Load Image");
76+
fireEvent.click(loadButton);
77+
78+
// Image should now be displayed (query by tag since it might be role="presentation")
79+
const image = screen.getByAltText("Test image");
80+
expect(image).toBeInTheDocument();
81+
expect(image).toHaveAttribute("src", testUrl);
82+
expect(image).toHaveAttribute("alt", "Test image");
83+
84+
// Warning message should be gone
85+
expect(screen.queryByText(/Image blocked for security/)).not.toBeInTheDocument();
86+
});
87+
88+
it("should handle image load errors", async () => {
89+
const testUrl = "https://example.com/broken-image.jpg";
90+
render(<SecureImageComponent src={testUrl} alt="broken image" />);
91+
92+
// Click load button
93+
const loadButton = screen.getByText("Load Image");
94+
fireEvent.click(loadButton);
95+
96+
// Simulate image error (query by alt text since role might be presentation)
97+
const image = screen.getByAltText("broken image");
98+
fireEvent.error(image);
99+
100+
// Should show error message and hide image
101+
await waitFor(() => {
102+
expect(screen.getByText(/Failed to load image/)).toBeInTheDocument();
103+
expect(screen.queryByAltText("broken image")).not.toBeInTheDocument();
104+
});
105+
106+
// Load button should be available again
107+
expect(screen.getByText("Load Image")).toBeInTheDocument();
108+
});
109+
110+
it("should pass through title and className props", () => {
111+
render(
112+
<SecureImageComponent
113+
src="https://example.com/image.jpg"
114+
alt="test image"
115+
title="Image title"
116+
className="custom-class"
117+
/>
118+
);
119+
120+
// Click load button
121+
fireEvent.click(screen.getByText("Load Image"));
122+
123+
// Check image has title (query by alt text)
124+
const image = screen.getByAltText("test image");
125+
expect(image).toHaveAttribute("title", "Image title");
126+
127+
// Check container has className
128+
const container = image.parentElement;
129+
expect(container).toHaveClass("custom-class");
130+
});
131+
});
132+
133+
describe("Security features", () => {
134+
it("should display query parameters as JSON for transparency", () => {
135+
render(
136+
<SecureImageComponent
137+
src="https://malicious.com/[email protected]&id=12345&action=view"
138+
/>
139+
);
140+
141+
// Should show all parameters clearly
142+
expect(screen.getByText(/Warning: URL contains query parameters/)).toBeInTheDocument();
143+
144+
// Check JSON is properly formatted
145+
const preElement = screen.getByText(/"email": "user@example.com"/);
146+
expect(preElement).toBeInTheDocument();
147+
expect(screen.getByText(/"id": "12345"/)).toBeInTheDocument();
148+
expect(screen.getByText(/"action": "view"/)).toBeInTheDocument();
149+
});
150+
151+
it("should handle encoded query parameters", () => {
152+
render(
153+
<SecureImageComponent
154+
src="https://example.com/img.png?data=%7B%22user%22%3A%22test%22%7D"
155+
/>
156+
);
157+
158+
// Should decode and display the parameter
159+
expect(screen.getByText(/Warning: URL contains query parameters/)).toBeInTheDocument();
160+
// The decoded value should be shown in the pre element
161+
const preElement = document.querySelector('pre');
162+
expect(preElement).toBeTruthy();
163+
// Check that the JSON contains the decoded data
164+
expect(preElement?.textContent).toContain('"data"');
165+
// The value is decoded as a string containing JSON
166+
expect(preElement?.textContent).toContain('"{');
167+
expect(preElement?.textContent).toContain('user');
168+
expect(preElement?.textContent).toContain('test');
169+
});
170+
});
171+
172+
describe("Alt text handling", () => {
173+
it("should use empty string for alt when not provided", () => {
174+
render(<SecureImageComponent src="https://example.com/image.jpg" />);
175+
176+
fireEvent.click(screen.getByText("Load Image"));
177+
178+
// Query by tag name since empty alt makes it role="presentation"
179+
const images = document.querySelectorAll('img');
180+
expect(images.length).toBe(1);
181+
expect(images[0]).toHaveAttribute("alt", "");
182+
});
183+
184+
it("should use provided alt text", () => {
185+
render(
186+
<SecureImageComponent
187+
src="https://example.com/image.jpg"
188+
alt="Description of image"
189+
/>
190+
);
191+
192+
fireEvent.click(screen.getByText("Load Image"));
193+
194+
const image = screen.getByAltText("Description of image");
195+
expect(image).toHaveAttribute("alt", "Description of image");
196+
});
197+
});
198+
});

0 commit comments

Comments
 (0)