Skip to content

Commit f2af98f

Browse files
ci: refine reports pipeline and stabilize c2pa tests
1 parent 7795932 commit f2af98f

File tree

12 files changed

+189
-29
lines changed

12 files changed

+189
-29
lines changed

citations.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2952,3 +2952,61 @@ Not run locally here (follow-up: `./mvnw -B -ntp clean test && ./mvnw -B -ntp ja
29522952

29532953
### **Attribution Statement**
29542954
> Portions of this work were generated with assistance from OpenAI ChatGPT (GPT-5) on 2026-02-17. All AI-generated content was reviewed and finalized by the development team.
2955+
### **Commit / Ticket Reference**
2956+
- **Commit:** pending
2957+
- **Ticket:** none
2958+
- **Date:** 2026-02-17
2959+
- **Team Member:** Jalen Stephens
2960+
2961+
---
2962+
2963+
### **AI Tool Information**
2964+
- **Tool Used:** OpenAI ChatGPT (GPT-5) via Codex CLI
2965+
- **Access Method:** Local Codex CLI (sandboxed)
2966+
- **Configuration:** Default model settings
2967+
- **Cost:** $0 (no paid API calls)
2968+
2969+
---
2970+
2971+
### **Purpose of AI Assistance**
2972+
Extended CI and test reliability:
2973+
- Refined `ci-reports.yml` to run Maven tests from module root, generate JaCoCo/PMD HTML, convert to PNG, and bundle reports into a single artifact.
2974+
- Updated `scripts/html_to_png.sh` to collect HTML assets and PNG snapshots under `reports/`.
2975+
- Made C2PA integration tests portable with JSON fixtures when the macOS `c2patool` binary is unavailable; exposed a parsing helper.
2976+
- Fixed Checkstyle issues (import order, indentation, wrapping) across FeatureExtractor, C2PA unit/integration tests, ImageControllerTest, SupabaseStorageServiceTest, AnalyzeServiceTest, AuthProxyServiceTest.
2977+
2978+
---
2979+
2980+
### **Prompts / Interaction Summary**
2981+
- “It doesn’t correctly run unit tests from the module root; collect JaCoCo + PMD HTML and PNG artifacts.”
2982+
- “Convert HTML → PNG via wkhtmltoimage and package reports.”
2983+
- “Make the C2PA integration tests work on all systems.”
2984+
- “Fix the Checkstyle warnings (import order, operator wrap, indentation).”
2985+
- “Commit message and fill out the citations template.”
2986+
2987+
---
2988+
2989+
### **Resulting Artifacts**
2990+
- `.github/workflows/ci-reports.yml`
2991+
- `scripts/html_to_png.sh`
2992+
- `src/main/java/dev/coms4156/project/metadetect/service/FeatureExtractor.java`
2993+
- `src/test/java/dev/coms4156/project/metadetect/c2pa/C2paToolInvokerIntegrationTest.java`
2994+
- `src/test/java/dev/coms4156/project/metadetect/c2pa/C2paToolInvokerUnitTest.java`
2995+
- `src/test/java/dev/coms4156/project/metadetect/controller/ImageControllerTest.java`
2996+
- `src/test/java/dev/coms4156/project/metadetect/service/SupabaseStorageServiceTest.java`
2997+
- `src/test/java/dev/coms4156/project/metadetect/service/AnalyzeServiceTest.java`
2998+
- `src/test/java/dev/coms4156/project/metadetect/service/AuthProxyServiceTest.java`
2999+
- `src/test/resources/c2pa-fixtures/*.json`
3000+
3001+
---
3002+
3003+
### **Verification**
3004+
- Local: `./mvnw -q -DskipTests compile` (passes in sandbox).
3005+
- Follow-up recommended: `./mvnw -B -ntp clean test jacoco:report pmd:pmd -Dpmd.failOnViolation=false` and `./mvnw -B -ntp pmd:check` to enforce gates.
3006+
3007+
---
3008+
3009+
### **Attribution Statement**
3010+
> Portions of this work were generated with assistance from OpenAI ChatGPT (GPT-5) on 2026-02-17. All AI-generated content was reviewed and finalized by the development team.
3011+
3012+
---

src/main/java/dev/coms4156/project/metadetect/c2pa/C2paToolInvoker.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,14 @@ && isAiClaimGenerator(claimGenerator)) {
249249
}
250250
}
251251

252+
/**
253+
* Exposed for tests to validate JSON parsing on platforms where the bundled
254+
* c2patool binary cannot execute (e.g., non-macOS CI runners).
255+
*/
256+
public C2paMetadata parseMetadataFromJsonForTests(String json) {
257+
return parseMetadataFromJson(json);
258+
}
259+
252260
private static boolean isAiClaimGenerator(String generator) {
253261
if (generator == null) {
254262
return false;
@@ -362,4 +370,4 @@ public String getc2paErrorMessage() {
362370
return c2paErrorMessage;
363371
}
364372
}
365-
}
373+
}

src/main/java/dev/coms4156/project/metadetect/service/FeatureExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dev.coms4156.project.metadetect.c2pa.C2paToolInvoker.C2paMetadata;
44
import java.util.ArrayList;
55
import java.util.List;
6+
import nu.pattern.OpenCV;
67
import org.opencv.core.Core;
78
import org.opencv.core.CvType;
89
import org.opencv.core.Mat;
@@ -12,7 +13,6 @@
1213
import org.opencv.core.Size;
1314
import org.opencv.imgcodecs.Imgcodecs;
1415
import org.opencv.imgproc.Imgproc;
15-
import nu.pattern.OpenCV;
1616

1717

1818
/**

src/test/java/dev/coms4156/project/metadetect/c2pa/C2paToolInvokerIntegrationTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.nio.file.Files;
1212
import java.nio.file.Path;
1313
import java.nio.file.StandardCopyOption;
14+
import java.util.Objects;
1415
import org.junit.jupiter.api.Test;
1516
import org.junit.jupiter.api.io.TempDir;
1617

@@ -25,6 +26,14 @@
2526
*/
2627
public class C2paToolInvokerIntegrationTest {
2728

29+
/** True when host OS can execute the bundled macOS c2patool (local dev). */
30+
private boolean c2paToolSupported() {
31+
String os = System.getProperty("os.name", "").toLowerCase();
32+
boolean isMac = os.contains("mac");
33+
File tool = new File("tools/c2patool/c2patool");
34+
return isMac && tool.exists() && tool.canExecute();
35+
}
36+
2837
/** Resolve c2patool binary from repository (no system install needed). */
2938
private File resolveLocalTool() {
3039
File tool = new File("tools/c2patool/c2patool");
@@ -40,8 +49,34 @@ private File resolveResource(String name) {
4049
return new File(url.getFile());
4150
}
4251

52+
/** Load JSON fixture for non-native environments. */
53+
private String loadFixtureJson(String name) {
54+
try (var in = getClass().getClassLoader().getResourceAsStream("c2pa-fixtures/" + name)) {
55+
Objects.requireNonNull(in, "Fixture not found: c2pa-fixtures/" + name);
56+
return new String(in.readAllBytes());
57+
} catch (IOException e) {
58+
throw new RuntimeException("Unable to load fixture " + name, e);
59+
}
60+
}
61+
4362
@Test
4463
void validAiImage_hasManifest_andAiClaimGenerator() {
64+
if (!c2paToolSupported()) {
65+
// Fallback: validate JSON parsing on non-macOS runners
66+
C2paToolInvoker invoker = new C2paToolInvoker("unused");
67+
C2paMetadata meta = invoker.parseMetadataFromJsonForTests(
68+
loadFixtureJson("valid_ai_output.json"));
69+
70+
assertNotNull(meta);
71+
assertEquals(1, meta.getc2paHasManifest());
72+
assertTrue(meta.getc2paManifestCount() >= 1);
73+
assertEquals(0, meta.getc2paErrorFlag());
74+
assertNull(meta.getc2paErrorMessage());
75+
assertNotNull(meta.getc2paClaimGenerator());
76+
assertEquals(1, meta.getc2paClaimGeneratorIsAi());
77+
return;
78+
}
79+
4580
File tool = resolveLocalTool();
4681
C2paToolInvoker invoker = new C2paToolInvoker(tool.getAbsolutePath());
4782

@@ -65,6 +100,19 @@ void validAiImage_hasManifest_andAiClaimGenerator() {
65100

66101
@Test
67102
void tamperedAiImage_stillHasManifest_butSameSchema(@TempDir Path tmp) throws IOException {
103+
if (!c2paToolSupported()) {
104+
C2paToolInvoker invoker = new C2paToolInvoker("unused");
105+
C2paMetadata meta = invoker.parseMetadataFromJsonForTests(
106+
loadFixtureJson("tampered_ai_output.json"));
107+
108+
assertNotNull(meta);
109+
assertEquals(1, meta.getc2paHasManifest());
110+
assertTrue(meta.getc2paManifestCount() >= 1);
111+
assertEquals(0, meta.getc2paErrorFlag());
112+
assertNotNull(meta.getc2paClaimGenerator());
113+
assertEquals(1, meta.getc2paClaimGeneratorIsAi());
114+
return;
115+
}
68116

69117
File validImage = resolveResource("ai_dawg_valid.png");
70118

@@ -103,6 +151,18 @@ void tamperedAiImage_stillHasManifest_butSameSchema(@TempDir Path tmp) throws IO
103151

104152
@Test
105153
void noManifestImage_returnsSoftSuccess_noError() {
154+
if (!c2paToolSupported()) {
155+
C2paToolInvoker invoker = new C2paToolInvoker("unused");
156+
C2paMetadata meta = invoker.parseMetadataFromJsonForTests(
157+
loadFixtureJson("no_manifest_output.json"));
158+
159+
assertNotNull(meta);
160+
assertEquals(0, meta.getc2paHasManifest());
161+
assertEquals(0, meta.getc2paManifestCount());
162+
assertEquals(0, meta.getc2paErrorFlag());
163+
assertNull(meta.getc2paErrorMessage());
164+
return;
165+
}
106166

107167
File tool = resolveLocalTool();
108168
C2paToolInvoker invoker = new C2paToolInvoker(tool.getAbsolutePath());

src/test/java/dev/coms4156/project/metadetect/c2pa/C2paToolInvokerUnitTest.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
import dev.coms4156.project.metadetect.c2pa.C2paToolInvoker.C2paMetadata;
88
import java.io.File;
9-
import java.nio.charset.StandardCharsets;
9+
import java.io.FileWriter;
1010
import java.lang.reflect.Method;
11-
import org.junit.jupiter.api.Test;
12-
import org.junit.jupiter.api.io.TempDir;
11+
import java.nio.charset.StandardCharsets;
1312
import java.nio.file.Files;
1413
import java.nio.file.Path;
15-
import java.io.FileWriter;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.io.TempDir;
1616

1717
/**
1818
* Unit-level coverage for {@link C2paToolInvoker} without calling the real CLI.
@@ -318,9 +318,11 @@ void extractMetadata_emptyStdout_returnsError(@TempDir Path tmp) throws Exceptio
318318
@Test
319319
void extractMetadata_noClaimFound_returnsSoftNoManifest(@TempDir Path tmp) throws Exception {
320320
Path script = tmp.resolve("c2pa-noclaim.sh");
321-
Files.writeString(script, "#!/bin/sh\n" +
322-
"echo \"no claim found\" 1>&2\n" +
323-
"exit 1\n", StandardCharsets.UTF_8);
321+
Files.writeString(script, """
322+
#!/bin/sh
323+
echo "no claim found" 1>&2
324+
exit 1
325+
""", StandardCharsets.UTF_8);
324326
script.toFile().setExecutable(true);
325327

326328
C2paToolInvoker invoker = new C2paToolInvoker(script.toAbsolutePath().toString());
@@ -335,9 +337,11 @@ void extractMetadata_noClaimFound_returnsSoftNoManifest(@TempDir Path tmp) throw
335337
@Test
336338
void extractMetadata_exitNonZero_withDifferentStderr_isError(@TempDir Path tmp) throws Exception {
337339
Path script = tmp.resolve("c2pa-fail.sh");
338-
Files.writeString(script, "#!/bin/sh\n" +
339-
"echo \"bad\" 1>&2\n" +
340-
"exit 2\n", StandardCharsets.UTF_8);
340+
Files.writeString(script, """
341+
#!/bin/sh
342+
echo "bad" 1>&2
343+
exit 2
344+
""", StandardCharsets.UTF_8);
341345
script.toFile().setExecutable(true);
342346

343347
C2paToolInvoker invoker = new C2paToolInvoker(script.toAbsolutePath().toString());
@@ -351,9 +355,11 @@ void extractMetadata_exitNonZero_withDifferentStderr_isError(@TempDir Path tmp)
351355
@Test
352356
void extractMetadata_stdoutEmpty_returnsError(@TempDir Path tmp) throws Exception {
353357
Path script = tmp.resolve("c2pa-empty-stdout.sh");
354-
Files.writeString(script, "#!/bin/sh\n" +
355-
"echo \"\" > /dev/null\n" +
356-
"exit 0\n", StandardCharsets.UTF_8);
358+
Files.writeString(script, """
359+
#!/bin/sh
360+
echo "" > /dev/null
361+
exit 0
362+
""", StandardCharsets.UTF_8);
357363
script.toFile().setExecutable(true);
358364

359365
C2paToolInvoker invoker = new C2paToolInvoker(script.toAbsolutePath().toString());

src/test/java/dev/coms4156/project/metadetect/controller/ImageControllerTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.coms4156.project.metadetect.controller;
22

3+
import static org.hamcrest.Matchers.containsString;
34
import static org.mockito.ArgumentMatchers.any;
45
import static org.mockito.ArgumentMatchers.eq;
56
import static org.mockito.ArgumentMatchers.isNull;
@@ -10,7 +11,6 @@
1011
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
1112
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
1213
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
13-
import static org.hamcrest.Matchers.containsString;
1414

1515
import dev.coms4156.project.metadetect.dto.Dtos;
1616
import dev.coms4156.project.metadetect.model.Image;
@@ -76,11 +76,11 @@ void listImages_success() throws Exception {
7676
when(imageService.listByOwner(userId, 0, 5)).thenReturn(List.of(makeImage()));
7777

7878
mvc.perform(MockMvcRequestBuilders.get("/api/images"))
79-
.andExpect(status().isOk())
80-
.andExpect(jsonPath("$[0].id").value(imgId.toString()))
81-
.andExpect(jsonPath("$[0].filename").value("test.jpg"))
82-
.andExpect(jsonPath("$[0].userId").value(userId.toString()))
83-
.andExpect(jsonPath("$[0].labels[0]").value("tag1"))
79+
.andExpect(status().isOk())
80+
.andExpect(jsonPath("$[0].id").value(imgId.toString()))
81+
.andExpect(jsonPath("$[0].filename").value("test.jpg"))
82+
.andExpect(jsonPath("$[0].userId").value(userId.toString()))
83+
.andExpect(jsonPath("$[0].labels[0]").value("tag1"))
8484
.andExpect(jsonPath("$[0].note").value("hello"));
8585
}
8686

@@ -93,7 +93,7 @@ void listImages_outOfRangePagination_returnsEmptyList() throws Exception {
9393
when(imageService.listByOwner(userId, 0, 5)).thenReturn(List.of(makeImage()));
9494

9595
mvc.perform(MockMvcRequestBuilders.get("/api/images?page=5&size=10"))
96-
.andExpect(status().isOk())
96+
.andExpect(status().isOk())
9797
.andExpect(content().json("[]"));
9898
}
9999

@@ -111,9 +111,9 @@ void getImage_success() throws Exception {
111111
when(imageService.getById(userId, imgId)).thenReturn(makeImage());
112112

113113
mvc.perform(MockMvcRequestBuilders.get("/api/images/" + imgId))
114-
.andExpect(status().isOk())
115-
.andExpect(jsonPath("$.id").value(imgId.toString()))
116-
.andExpect(jsonPath("$.note").value("hello"))
114+
.andExpect(status().isOk())
115+
.andExpect(jsonPath("$.id").value(imgId.toString()))
116+
.andExpect(jsonPath("$.note").value("hello"))
117117
.andExpect(jsonPath("$.labels[1]").value("tag2"));
118118
}
119119

@@ -183,7 +183,7 @@ void updateImage_nullLabels_branchCovered() throws Exception {
183183
.andExpect(status().isOk())
184184
.andExpect(jsonPath("$.note").value("note-only"))
185185
.andExpect(jsonPath("$.labels").isArray())
186-
.andExpect(jsonPath("$.labels").isEmpty());
186+
.andExpect(jsonPath("$.labels").isEmpty());
187187
}
188188

189189
// ---- DELETE /api/images/{id} ----

src/test/java/dev/coms4156/project/metadetect/service/AnalyzeServiceTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,8 @@ void downloadToTemp_usesStoragePathExtension_whenPresent() throws Exception {
477477
);
478478

479479
assertTrue(out.getName().endsWith(".png"));
480-
assertThat(Files.readAllBytes(out.toPath())).containsExactly("pngbytes".getBytes(StandardCharsets.UTF_8));
480+
assertThat(Files.readAllBytes(out.toPath()))
481+
.containsExactly("pngbytes".getBytes(StandardCharsets.UTF_8));
481482

482483
// cleanup
483484
out.delete();

src/test/java/dev/coms4156/project/metadetect/service/AuthProxyServiceTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertThrows;
5-
65
import static org.junit.jupiter.api.Assumptions.assumeTrue;
76

87
import java.io.IOException;

src/test/java/dev/coms4156/project/metadetect/service/SupabaseStorageServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
import static org.junit.jupiter.api.Assertions.assertNull;
66
import static org.junit.jupiter.api.Assumptions.assumeTrue;
77

8+
import java.net.SocketException;
89
import okhttp3.mockwebserver.MockResponse;
910
import okhttp3.mockwebserver.MockWebServer;
1011
import okhttp3.mockwebserver.RecordedRequest;
1112
import org.junit.jupiter.api.AfterEach;
1213
import org.junit.jupiter.api.BeforeEach;
1314
import org.junit.jupiter.api.Test;
14-
import java.net.SocketException;
1515
import org.springframework.http.MediaType;
1616
import org.springframework.web.reactive.function.client.WebClient;
1717

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"manifests": {}
3+
}

0 commit comments

Comments
 (0)