Skip to content

Commit c0e3de6

Browse files
Enhancement/404 page not found (#53)
* Add 404 error handling to `StaticFileHandler` with fallback page * Add test coverage for `StaticFileHandler` and improve constructor flexibility - Introduced a new constructor in `StaticFileHandler` to support custom web root paths, improving testability. - Added `StaticFileHandlerTest` to validate static file serving and error handling logic. * Add test for 404 handling in `StaticFileHandler` - Added a test to ensure nonexistent files return a 404 response. - Utilized a temporary directory and fallback file for improved test isolation. * Verify `Content-Type` header in `StaticFileHandlerTest` after running Pittest, mutations survived where setHeaders could be removed without test failure. * Remove unused `.btn` styles from `pageNotFound.html` * Improve 404 handling in `StaticFileHandler`: add fallback to plain text response if `pageNotFound.html` is missing, and fix typo in `pageNotFound.html`, after comments from CodeRabbit.
1 parent 8cc69d8 commit c0e3de6

File tree

3 files changed

+159
-4
lines changed

3 files changed

+159
-4
lines changed

src/main/java/org/example/StaticFileHandler.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,42 @@
1010
import java.util.Map;
1111

1212
public class StaticFileHandler {
13-
private static final String WEB_ROOT = "www";
13+
private final String WEB_ROOT;
1414
private byte[] fileBytes;
15+
private int statusCode;
1516

16-
public StaticFileHandler(){}
17+
//Constructor for production
18+
public StaticFileHandler() {
19+
WEB_ROOT = "www";
20+
}
21+
22+
//Constructor for tests, otherwise the www folder won't be seen
23+
public StaticFileHandler(String webRoot){
24+
WEB_ROOT = webRoot;
25+
}
1726

1827
private void handleGetRequest(String uri) throws IOException {
1928

2029
File file = new File(WEB_ROOT, uri);
21-
fileBytes = Files.readAllBytes(file.toPath());
22-
30+
if(file.exists()) {
31+
fileBytes = Files.readAllBytes(file.toPath());
32+
statusCode = 200;
33+
} else {
34+
File errorFile = new File(WEB_ROOT, "pageNotFound.html");
35+
if(errorFile.exists()) {
36+
fileBytes = Files.readAllBytes(errorFile.toPath());
37+
} else {
38+
fileBytes = "404 Not Found".getBytes();
39+
}
40+
statusCode = 404;
41+
}
2342
}
2443

2544
public void sendGetRequest(OutputStream outputStream, String uri) throws IOException{
2645
handleGetRequest(uri);
46+
2747
HttpResponseBuilder response = new HttpResponseBuilder();
48+
response.setStatusCode(statusCode);
2849
response.setHeaders(Map.of("Content-Type", "text/html; charset=utf-8"));
2950
response.setBody(fileBytes);
3051
PrintWriter writer = new PrintWriter(outputStream, true);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.example;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.io.TempDir;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
/**
13+
* Unit test class for verifying the behavior of the StaticFileHandler class.
14+
*
15+
* This test class ensures that StaticFileHandler correctly handles GET requests
16+
* for static files, including both cases where the requested file exists and
17+
* where it does not. Temporary directories and files are utilized in tests to
18+
* ensure no actual file system dependencies during test execution.
19+
*
20+
* Key functional aspects being tested include:
21+
* - Correct response status code and content for an existing file.
22+
* - Correct response status code and fallback behavior for a missing file.
23+
*/
24+
class StaticFileHandlerTest {
25+
26+
//Junit creates a temporary folder which can be filled with temporary files that gets removed after tests
27+
@TempDir
28+
Path tempDir;
29+
30+
31+
@Test
32+
void test_file_that_exists_should_return_200() throws IOException {
33+
//Arrange
34+
Path testFile = tempDir.resolve("test.html"); // Defines the path in the temp directory
35+
Files.writeString(testFile, "Hello Test"); // Creates a text in that file
36+
37+
//Using the new constructor in StaticFileHandler to reroute so the tests uses the temporary folder instead of the hardcoded www
38+
StaticFileHandler staticFileHandler = new StaticFileHandler(tempDir.toString());
39+
40+
//Using ByteArrayOutputStream instead of Outputstream during tests to capture the servers response in memory, fake stream
41+
ByteArrayOutputStream fakeOutput = new ByteArrayOutputStream();
42+
43+
//Act
44+
staticFileHandler.sendGetRequest(fakeOutput, "test.html"); //Get test.html and write the answer to fakeOutput
45+
46+
//Assert
47+
String response = fakeOutput.toString();//Converts the captured byte stream into a String for verification
48+
49+
assertTrue(response.contains("HTTP/1.1 200 OK")); // Assert the status
50+
assertTrue(response.contains("Hello Test")); //Assert the content in the file
51+
52+
assertTrue(response.contains("Content-Type: text/html; charset=utf-8")); // Verify the correct Content-type header
53+
54+
}
55+
56+
@Test
57+
void test_file_that_does_not_exists_should_return_404() throws IOException {
58+
//Arrange
59+
// Pre-create the mandatory error page in the temp directory to prevent NoSuchFileException
60+
Path testFile = tempDir.resolve("pageNotFound.html");
61+
Files.writeString(testFile, "Fallback page");
62+
63+
//Using the new constructor in StaticFileHandler to reroute so the tests uses the temporary folder instead of the hardcoded www
64+
StaticFileHandler staticFileHandler = new StaticFileHandler(tempDir.toString());
65+
66+
//Using ByteArrayOutputStream instead of Outputstream during tests to capture the servers response in memory, fake stream
67+
ByteArrayOutputStream fakeOutput = new ByteArrayOutputStream();
68+
69+
//Act
70+
staticFileHandler.sendGetRequest(fakeOutput, "notExistingFile.html"); // Request a file that clearly doesn't exist to trigger the 404 logic
71+
72+
//Assert
73+
String response = fakeOutput.toString();//Converts the captured byte stream into a String for verification
74+
75+
assertTrue(response.contains("HTTP/1.1 404 Not Found")); // Assert the status
76+
77+
}
78+
79+
}

www/pageNotFound.html

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>404 - Page Not Found</title>
7+
<style>
8+
body {
9+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10+
background-color: #f4f7f6;
11+
color: #333;
12+
display: flex;
13+
align-items: center;
14+
justify-content: center;
15+
height: 100vh;
16+
margin: 0;
17+
text-align: center;
18+
}
19+
.container {
20+
max-width: 600px;
21+
padding: 40px;
22+
background: white;
23+
border-radius: 12px;
24+
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
25+
}
26+
h1 {
27+
font-size: 100px;
28+
margin: 0;
29+
color: rgba(109, 21, 181, 0.6);
30+
line-height: 1;
31+
}
32+
h2 {
33+
font-size: 24px;
34+
margin-bottom: 20px;
35+
}
36+
p {
37+
color: #666;
38+
margin-bottom: 30px;
39+
line-height: 1.6;
40+
}
41+
.icon {
42+
font-size: 50px;
43+
margin-bottom: 20px;
44+
}
45+
</style>
46+
</head>
47+
<body>
48+
<div class="container">
49+
<div class="icon">🚀</div>
50+
<h1>404</h1>
51+
<h2>Woopsie daisy! Page went to the moon.</h2>
52+
<p>We cannot find the page you were looking for. Might have been moved, removed, or maybe it was kind of a useless link to begin with.</p>
53+
</div>
54+
</body>
55+
</html>

0 commit comments

Comments
 (0)