Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 74ebe5b

Browse files
committedJun 17, 2025
feat: Update Spring Boot, Java version, and add tests
Updates Spring Boot to version 3.5.0 and Java to version 17. This also introduces comprehensive unit tests for QR code generation and reading functionalities, covering both positive and negative scenarios. Key changes: - Updated `spring-boot-starter-parent` to 3.5.0 in `pom.xml`. - Updated `java.version` to 17 in `pom.xml`. - Updated Java version mentions in `README.md` to 17. - Replaced `javax.*` with `jakarta.*` namespaces for Spring Boot 3 compatibility. - Updated `springdoc-openapi-ui` to `springdoc-openapi-starter-webmvc-ui:2.5.0`. - Added `QrCodeServiceTests.java` with tests for: - Successful QR code generation. - QR code generation with null and empty inputs. - Successful QR code reading. - Reading invalid image formats and non-QR images. - Handling IOExceptions and unexpected content during QR reading. - Updated `README.md` to include a "Testing" section explaining the new tests and how to run them.
1 parent 960900c commit 74ebe5b

28 files changed

+472
-12
lines changed
 

‎README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# QR-code Generator and Reader
22

3-
## Application used [Java 15](https://onurdesk.com/what-are-preview-features-in-java-15/) | [Onurdesk](https://onurdesk.com/)
3+
## Application used [Java 17](https://onurdesk.com/what-are-preview-features-in-java-17/) and Spring Boot 3.5.0 | [Onurdesk](https://onurdesk.com/)
44

5-
###### Spring boot application exposing REST API endpoint to genrate QR-code representing custom message and another endpoint to read the decoded message, built using Java [Spring boot](https://onurdesk.com/category/spring/spring-boot/) and [google's zxing library](https://opensource.google/projects/zxing).
5+
###### Spring boot application exposing REST API endpoint to genrate QR-code representing custom message and another endpoint to read the decoded message, built using Java, [Spring Boot 3.5.0](https://spring.io/projects/spring-boot/) and [google's zxing library](https://opensource.google/projects/zxing).
66

77
<center>
88
<a target='_blank' href='https://spring-boot-qr-code-generator.herokuapp.com/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config'>Running Application</a>
99
</center>
1010

1111
## Local Setup
1212

13-
* Install [Java 15](https://onurdesk.com/what-are-preview-features-in-java-15/)
13+
* Install [Java 17](https://onurdesk.com/what-are-preview-features-in-java-17/)
1414
* Install [Maven](https://onurdesk.com/what-is-maven-plugin/)
1515

1616
Recommended way is to use [sdkman](https://sdkman.io/) for installing both maven and java
@@ -33,3 +33,27 @@ Go to the below url to view swagger-ui (API docs)
3333
```
3434
http://localhost:9090/swagger-ui.html
3535
```
36+
37+
## Testing
38+
39+
The core QR code generation and reading functionalities are tested in `src/test/java/com/onurdesk/iris/service/QrCodeServiceTests.java`. These tests cover:
40+
41+
* **Positive Scenarios:**
42+
* Successful QR code generation with valid text input.
43+
* Successful reading and decoding of a valid QR code image.
44+
* **Negative Scenarios:**
45+
* Attempting QR code generation with null or invalid DTO.
46+
* Handling of empty title during QR code generation.
47+
* Attempting to read invalid image files (not images or not QR codes).
48+
* Attempting to read QR codes with unexpected content (not deserializable to the expected DTO).
49+
* Handling I/O exceptions during file reading.
50+
51+
### Running Tests
52+
53+
You can run the tests using Maven:
54+
55+
```bash
56+
mvn test
57+
```
58+
59+
Alternatively, running `mvn clean install` will also execute the tests as part of the build lifecycle.

‎pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>org.springframework.boot</groupId>
88
<artifactId>spring-boot-starter-parent</artifactId>
9-
<version>2.5.0</version>
9+
<version>3.5.0</version>
1010
<relativePath /> <!-- lookup parent from repository -->
1111
</parent>
1212
<groupId>com.onurdesk</groupId>
@@ -15,7 +15,7 @@
1515
<name>spring-boot-qr-code-generator-reader</name>
1616
<description>Spring Boot Application exposing REST APi endpoints to generate QR code representing custom messages and another endpoint to read it.</description>
1717
<properties>
18-
<java.version>15</java.version>
18+
<java.version>17</java.version>
1919
</properties>
2020
<dependencies>
2121
<dependency>
@@ -43,8 +43,8 @@
4343
</dependency>
4444
<dependency>
4545
<groupId>org.springdoc</groupId>
46-
<artifactId>springdoc-openapi-ui</artifactId>
47-
<version>1.5.8</version>
46+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
47+
<version>2.5.0</version>
4848
</dependency>
4949
<dependency>
5050
<groupId>org.springframework.boot</groupId>

‎src/main/java/com/onurdesk/iris/controller/QrCodeController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import java.io.IOException;
44

5-
import javax.servlet.http.HttpServletResponse;
6-
import javax.validation.Valid;
5+
import jakarta.servlet.http.HttpServletResponse;
6+
import jakarta.validation.Valid;
77

88
import org.springframework.http.HttpStatus;
99
import org.springframework.http.ResponseEntity;

‎src/main/java/com/onurdesk/iris/dto/QrCodeGenerationRequestDto.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.onurdesk.iris.dto;
22

3-
import javax.validation.constraints.NotBlank;
4-
import javax.validation.constraints.Size;
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.Size;
55

66
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
77

‎src/main/java/com/onurdesk/iris/service/QrCodeService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.io.IOException;
66

77
import javax.imageio.ImageIO;
8-
import javax.servlet.http.HttpServletResponse;
8+
import jakarta.servlet.http.HttpServletResponse;
99

1010
import org.springframework.http.HttpHeaders;
1111
import org.springframework.http.ResponseEntity;
@@ -42,6 +42,7 @@ public void generate(final QrCodeGenerationRequestDto qrCodeGenerationRequestDto
4242
final HttpServletResponse httpServletResponse) throws IOException, WriterException {
4343
httpServletResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION,
4444
"attachment;filename=" + qrCodeGenerationRequestDto.getTitle().trim().replace(" ", "_") + ".png");
45+
httpServletResponse.setContentType("image/png"); // Explicitly set content type
4546

4647
final var outputStream = new BufferedOutputStream(httpServletResponse.getOutputStream());
4748
QRCodeWriter writer = new QRCodeWriter();
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.onurdesk.iris.service;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.google.zxing.BarcodeFormat;
5+
import com.google.zxing.WriterException;
6+
import com.google.zxing.client.j2se.MatrixToImageWriter;
7+
import com.google.zxing.common.BitMatrix;
8+
import com.google.zxing.qrcode.QRCodeWriter;
9+
import com.onurdesk.iris.dto.QrCodeGenerationRequestDto;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
import org.mockito.ArgumentCaptor;
14+
import org.mockito.InjectMocks;
15+
import org.mockito.Mock;
16+
import org.mockito.MockedStatic;
17+
import org.mockito.Mockito;
18+
import org.mockito.junit.jupiter.MockitoExtension;
19+
import org.springframework.http.HttpHeaders;
20+
import org.springframework.http.HttpStatus;
21+
import org.springframework.http.ResponseEntity;
22+
import org.springframework.mock.web.MockHttpServletResponse;
23+
import org.springframework.web.multipart.MultipartFile;
24+
25+
import javax.imageio.ImageIO;
26+
import jakarta.servlet.ServletOutputStream;
27+
import java.awt.image.BufferedImage;
28+
import java.io.ByteArrayInputStream;
29+
import java.io.ByteArrayOutputStream;
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
33+
import static org.junit.jupiter.api.Assertions.*;
34+
import static org.mockito.ArgumentMatchers.any;
35+
import static org.mockito.ArgumentMatchers.anyInt;
36+
import static org.mockito.ArgumentMatchers.anyString;
37+
import static org.mockito.Mockito.*;
38+
39+
@ExtendWith(MockitoExtension.class)
40+
class QrCodeServiceTests {
41+
42+
@InjectMocks
43+
private QrCodeService qrCodeService;
44+
45+
private QrCodeGenerationRequestDto sampleDto;
46+
private ObjectMapper objectMapper = new ObjectMapper();
47+
48+
@BeforeEach
49+
void setUp() {
50+
sampleDto = QrCodeGenerationRequestDto.builder()
51+
.title("Test QR")
52+
.message("This is a test payload") // Assuming 'payload' maps to 'message' in DTO based on schema
53+
.generatedByName("JUnit") // Assuming 'generatedBy' maps to 'generatedByName'
54+
.generatedForName("Test Target") // Adding a value for this field
55+
.build();
56+
}
57+
58+
@Test
59+
void testGenerateQrCode_success() throws IOException, WriterException {
60+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
61+
62+
qrCodeService.generate(sampleDto, mockResponse);
63+
64+
assertEquals("attachment;filename=Test_QR.png", mockResponse.getHeader(HttpHeaders.CONTENT_DISPOSITION));
65+
assertEquals("image/png", mockResponse.getContentType()); // MatrixToImageWriter sets this implicitly
66+
67+
// Verify the output stream contains PNG data (basic check for non-empty)
68+
assertTrue(mockResponse.getContentAsByteArray().length > 0);
69+
70+
// Further verification could involve trying to read the byte array as an image
71+
// and potentially decoding it, but that might be too much for a unit test.
72+
// For now, we trust that if MatrixToImageWriter.writeToStream ran without error
73+
// and produced bytes, it's likely correct.
74+
75+
// We can also try to verify the content of the QR code if we mock the writer part.
76+
// Let's try to capture the string passed to the QRCodeWriter.encode
77+
// This requires QRCodeWriter to be a mock or using a static mock for MatrixToImageWriter
78+
// For simplicity, the current check on headers and non-empty output is a good start.
79+
}
80+
81+
@Test
82+
void testGenerateQrCode_nullDto() {
83+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
84+
assertThrows(NullPointerException.class, () -> {
85+
// The ObjectMapper().writeValueAsString(null) will throw NullPointerException
86+
qrCodeService.generate(null, mockResponse);
87+
});
88+
}
89+
90+
@Test
91+
void testGenerateQrCode_emptyTitleInDto() throws IOException, WriterException {
92+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
93+
QrCodeGenerationRequestDto dtoWithEmptyTitle = QrCodeGenerationRequestDto.builder()
94+
.title("") // Empty title
95+
.message("Some payload")
96+
.generatedByName("JUnit")
97+
.generatedForName("Test Target")
98+
.build();
99+
100+
qrCodeService.generate(dtoWithEmptyTitle, mockResponse);
101+
102+
// Expecting "attachment;filename=.png" or similar, depending on implementation logic for empty title
103+
assertEquals("attachment;filename=.png", mockResponse.getHeader(HttpHeaders.CONTENT_DISPOSITION));
104+
assertTrue(mockResponse.getContentAsByteArray().length > 0);
105+
}
106+
107+
108+
@Test
109+
void testReadQrCode_success() throws Exception {
110+
// 1. Prepare a valid QR code image as byte array
111+
String originalContent = objectMapper.writeValueAsString(sampleDto);
112+
QRCodeWriter qrCodeWriter = new QRCodeWriter();
113+
BitMatrix bitMatrix = qrCodeWriter.encode(originalContent, BarcodeFormat.QR_CODE, 200, 200);
114+
ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream();
115+
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", pngOutputStream);
116+
byte[] qrCodeBytes = pngOutputStream.toByteArray();
117+
118+
// 2. Mock MultipartFile
119+
MultipartFile multipartFile = mock(MultipartFile.class);
120+
when(multipartFile.getInputStream()).thenReturn(new ByteArrayInputStream(qrCodeBytes));
121+
122+
// 3. Call read method
123+
ResponseEntity<?> responseEntity = qrCodeService.read(multipartFile);
124+
125+
// 4. Assertions
126+
assertNotNull(responseEntity);
127+
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
128+
assertTrue(responseEntity.getBody() instanceof QrCodeGenerationRequestDto);
129+
QrCodeGenerationRequestDto resultDto = (QrCodeGenerationRequestDto) responseEntity.getBody();
130+
assertEquals(sampleDto.getTitle(), resultDto.getTitle());
131+
assertEquals(sampleDto.getMessage(), resultDto.getMessage()); // Changed from getPayload to getMessage
132+
assertEquals(sampleDto.getGeneratedByName(), resultDto.getGeneratedByName()); // Changed from getGeneratedBy
133+
}
134+
135+
@Test
136+
void testReadQrCode_invalidImageFormat() throws IOException {
137+
MultipartFile multipartFile = mock(MultipartFile.class);
138+
// Simulate a file that is not a valid image (e.g., random bytes)
139+
byte[] invalidImageBytes = "This is not an image".getBytes();
140+
when(multipartFile.getInputStream()).thenReturn(new ByteArrayInputStream(invalidImageBytes));
141+
142+
// ImageIO.read is expected to return null for non-image formats it doesn't understand
143+
// which will then cause NullPointerException in BufferedImageLuminanceSource constructor
144+
assertThrows(NullPointerException.class, () -> {
145+
qrCodeService.read(multipartFile);
146+
}, "Should throw NullPointerException when ImageIO.read returns null for invalid image format that is not decodable by ImageIO");
147+
}
148+
149+
@Test
150+
void testReadQrCode_notAQrCode() throws IOException {
151+
// Create a valid PNG image, but not a QR code (e.g., a blank image)
152+
BufferedImage blankImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
153+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
154+
ImageIO.write(blankImage, "png", baos);
155+
byte[] blankImageBytes = baos.toByteArray();
156+
157+
MultipartFile multipartFile = mock(MultipartFile.class);
158+
when(multipartFile.getInputStream()).thenReturn(new ByteArrayInputStream(blankImageBytes));
159+
160+
// Expect NotFoundException because MultiFormatReader won't find a QR code
161+
assertThrows(com.google.zxing.NotFoundException.class, () -> {
162+
qrCodeService.read(multipartFile);
163+
});
164+
}
165+
166+
@Test
167+
void testReadQrCode_ioExceptionOnInputStream() throws IOException {
168+
MultipartFile multipartFile = mock(MultipartFile.class);
169+
when(multipartFile.getInputStream()).thenThrow(new IOException("Failed to read input stream"));
170+
171+
assertThrows(IOException.class, () -> {
172+
qrCodeService.read(multipartFile);
173+
});
174+
}
175+
176+
@Test
177+
void testReadQrCode_unexpectedContent() throws Exception {
178+
// 1. Prepare a QR code with content that is not a valid JSON for QrCodeGenerationRequestDto
179+
String nonJsonContent = "Just some plain text, not JSON";
180+
QRCodeWriter qrCodeWriter = new QRCodeWriter();
181+
BitMatrix bitMatrix = qrCodeWriter.encode(nonJsonContent, BarcodeFormat.QR_CODE, 200, 200);
182+
ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream();
183+
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", pngOutputStream);
184+
byte[] qrCodeBytes = pngOutputStream.toByteArray();
185+
186+
// 2. Mock MultipartFile
187+
MultipartFile multipartFile = mock(MultipartFile.class);
188+
when(multipartFile.getInputStream()).thenReturn(new ByteArrayInputStream(qrCodeBytes));
189+
190+
// 3. Call read method and expect a Jackson mapping/parsing exception
191+
// The service tries to map result.getText() to QrCodeGenerationRequestDto.class
192+
// This will fail if the text is not a JSON representation of that DTO.
193+
assertThrows(com.fasterxml.jackson.core.JsonProcessingException.class, () -> { // Changed to broader JsonProcessingException
194+
qrCodeService.read(multipartFile);
195+
});
196+
}
197+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"groups": [
3+
{
4+
"name": "com.onurdesk.iris.swagger",
5+
"type": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties",
6+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties"
7+
},
8+
{
9+
"name": "com.onurdesk.iris.swagger.properties",
10+
"type": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties",
11+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties",
12+
"sourceMethod": "getProperties()"
13+
},
14+
{
15+
"name": "com.onurdesk.iris.swagger.properties.contact",
16+
"type": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties$Contact",
17+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties",
18+
"sourceMethod": "getContact()"
19+
}
20+
],
21+
"properties": [
22+
{
23+
"name": "com.onurdesk.iris.swagger.properties.api-version",
24+
"type": "java.lang.String",
25+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties"
26+
},
27+
{
28+
"name": "com.onurdesk.iris.swagger.properties.contact.email",
29+
"type": "java.lang.String",
30+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties$Contact"
31+
},
32+
{
33+
"name": "com.onurdesk.iris.swagger.properties.contact.name",
34+
"type": "java.lang.String",
35+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties$Contact"
36+
},
37+
{
38+
"name": "com.onurdesk.iris.swagger.properties.contact.url",
39+
"type": "java.lang.String",
40+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties$Contact"
41+
},
42+
{
43+
"name": "com.onurdesk.iris.swagger.properties.description",
44+
"type": "java.lang.String",
45+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties"
46+
},
47+
{
48+
"name": "com.onurdesk.iris.swagger.properties.title",
49+
"type": "java.lang.String",
50+
"sourceType": "com.onurdesk.iris.configuration.properties.OpenApiConfigurationProperties$Properties"
51+
}
52+
],
53+
"hints": [],
54+
"ignored": {
55+
"properties": []
56+
}
57+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
META-INF/spring-configuration-metadata.json
2+
com/onurdesk/iris/dto/QrCodeGenerationRequestDto$QrCodeGenerationRequestDtoBuilder.class
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/app/src/main/java/com/onurdesk/iris/SpringBootQrCodeGeneratorReaderApplication.java
2+
/app/src/main/java/com/onurdesk/iris/configuration/OpenApiConfiguration.java
3+
/app/src/main/java/com/onurdesk/iris/configuration/properties/OpenApiConfigurationProperties.java
4+
/app/src/main/java/com/onurdesk/iris/controller/QrCodeController.java
5+
/app/src/main/java/com/onurdesk/iris/dto/QrCodeGenerationRequestDto.java
6+
/app/src/main/java/com/onurdesk/iris/exception/handler/GenericExceptionHandler.java
7+
/app/src/main/java/com/onurdesk/iris/exception/handler/ValidationFailureExceptionHandler.java
8+
/app/src/main/java/com/onurdesk/iris/service/QrCodeService.java
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com/onurdesk/iris/service/QrCodeServiceTests.class
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/app/src/test/java/com/onurdesk/iris/SpringBootQrCodeGeneratorReaderApplicationTests.java
2+
/app/src/test/java/com/onurdesk/iris/service/QrCodeServiceTests.java

‎target/surefire-reports/TEST-com.onurdesk.iris.SpringBootQrCodeGeneratorReaderApplicationTests.xml

Lines changed: 89 additions & 0 deletions
Large diffs are not rendered by default.

‎target/surefire-reports/TEST-com.onurdesk.iris.service.QrCodeServiceTests.xml

Lines changed: 71 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-------------------------------------------------------------------------------
2+
Test set: com.onurdesk.iris.SpringBootQrCodeGeneratorReaderApplicationTests
3+
-------------------------------------------------------------------------------
4+
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.685 s -- in com.onurdesk.iris.SpringBootQrCodeGeneratorReaderApplicationTests
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-------------------------------------------------------------------------------
2+
Test set: com.onurdesk.iris.service.QrCodeServiceTests
3+
-------------------------------------------------------------------------------
4+
Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.827 s -- in com.onurdesk.iris.service.QrCodeServiceTests
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.