Skip to content

Commit d4b622b

Browse files
ChrisEdwardsclaude
andcommitted
Add anonymous builder pattern for test mock objects
Implement anonymous builders following established pattern from AnonymousApplicationBuilder to reduce test boilerplate and improve readability. Created 6 builders total (3 in this commit, 3 previously): New builders in this commit: - AnonymousMetadataItemBuilder: Simple metadata item mocking - AnonymousTraceBuilder: Complex trace mocking with SessionMetadata helpers - AnonymousProjectBuilder: SAST Project interface mocking Previously committed builders: - AnonymousSessionMetadataBuilder: Session metadata with MetadataItem lists - AnonymousScanBuilder: SAST Scan interface mocking with SARIF support - AnonymousLibraryExtendedBuilder: SCA library mocking with many fields Refactored test files: - AssessServiceTest: 20+ trace mocks simplified (SessionMetadata patterns) - SastServiceTest: All 5 Project and 1 Scan mocks refactored - SCAServiceTest: All 3 LibraryExtended helper methods refactored Benefits: - Reduces 10+ lines of mock setup to 1-3 lines per object - Lenient stubbing avoids UnnecessaryStubbingException - Fluent API improves test readability - Convenience methods simplify complex nested object creation All 278 tests pass. Addresses mcp-hyj. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 4f8fbb3 commit d4b622b

File tree

6 files changed

+737
-354
lines changed

6 files changed

+737
-354
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.contrast.labs.ai.mcp.contrast;
2+
3+
import static org.mockito.Mockito.lenient;
4+
import static org.mockito.Mockito.mock;
5+
6+
import com.contrastsecurity.models.MetadataItem;
7+
import java.util.UUID;
8+
9+
/**
10+
* Builder for creating anonymous MetadataItem mocks with sensible defaults. Only override fields
11+
* that matter for your specific test.
12+
*
13+
* <p>Example usage:
14+
*
15+
* <pre>
16+
* MetadataItem item = AnonymousMetadataItemBuilder.validMetadataItem()
17+
* .withDisplayLabel("ENVIRONMENT")
18+
* .withValue("PRODUCTION")
19+
* .build();
20+
* </pre>
21+
*/
22+
public class AnonymousMetadataItemBuilder {
23+
private final MetadataItem metadataItem;
24+
private String displayLabel = "Label-" + UUID.randomUUID().toString().substring(0, 8);
25+
private String agentLabel = "agentLabel-" + UUID.randomUUID().toString().substring(0, 8);
26+
private String value = "Value-" + UUID.randomUUID().toString().substring(0, 8);
27+
28+
private AnonymousMetadataItemBuilder() {
29+
this.metadataItem = mock(MetadataItem.class);
30+
}
31+
32+
/** Create a builder with valid defaults for all required fields. */
33+
public static AnonymousMetadataItemBuilder validMetadataItem() {
34+
return new AnonymousMetadataItemBuilder();
35+
}
36+
37+
public AnonymousMetadataItemBuilder withDisplayLabel(String displayLabel) {
38+
this.displayLabel = displayLabel;
39+
return this;
40+
}
41+
42+
public AnonymousMetadataItemBuilder withAgentLabel(String agentLabel) {
43+
this.agentLabel = agentLabel;
44+
return this;
45+
}
46+
47+
public AnonymousMetadataItemBuilder withValue(String value) {
48+
this.value = value;
49+
return this;
50+
}
51+
52+
/**
53+
* Build the MetadataItem mock with all configured values. Uses lenient stubbing to avoid
54+
* UnnecessaryStubbingException for fields not accessed in specific tests.
55+
*/
56+
public MetadataItem build() {
57+
lenient().when(metadataItem.getDisplayLabel()).thenReturn(displayLabel);
58+
lenient().when(metadataItem.getAgentLabel()).thenReturn(agentLabel);
59+
lenient().when(metadataItem.getValue()).thenReturn(value);
60+
return metadataItem;
61+
}
62+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package com.contrast.labs.ai.mcp.contrast;
2+
3+
import static org.mockito.Mockito.lenient;
4+
import static org.mockito.Mockito.mock;
5+
6+
import com.contrastsecurity.sdk.scan.Project;
7+
import java.time.Instant;
8+
import java.util.ArrayList;
9+
import java.util.Collection;
10+
import java.util.UUID;
11+
12+
/**
13+
* Builder for creating anonymous Project mocks with sensible defaults. Only override fields that
14+
* matter for your specific test.
15+
*
16+
* <p>Note: Project is an interface, so this builder mocks the interface methods (not getters).
17+
*
18+
* <p>Example usage:
19+
*
20+
* <pre>
21+
* Project project = AnonymousProjectBuilder.validProject()
22+
* .withName("MyProject")
23+
* .withId("project-123")
24+
* .build();
25+
* </pre>
26+
*/
27+
public class AnonymousProjectBuilder {
28+
private final Project project;
29+
private String id = "project-" + UUID.randomUUID().toString().substring(0, 8);
30+
private String name = "Project-" + UUID.randomUUID().toString().substring(0, 8);
31+
private String organizationId = "org-" + UUID.randomUUID().toString().substring(0, 8);
32+
private String language = "Java";
33+
private boolean archived = false;
34+
private int critical = 0;
35+
private int high = 0;
36+
private int medium = 0;
37+
private int low = 0;
38+
private int note = 0;
39+
private String lastScanId = "scan-" + UUID.randomUUID().toString().substring(0, 8);
40+
private Instant lastScanTime = Instant.now();
41+
private int completedScans = 1;
42+
private Collection<String> includeNamespaceFilters = new ArrayList<>();
43+
private Collection<String> excludeNamespaceFilters = new ArrayList<>();
44+
45+
private AnonymousProjectBuilder() {
46+
this.project = mock(Project.class);
47+
}
48+
49+
/** Create a builder with valid defaults for all required fields. */
50+
public static AnonymousProjectBuilder validProject() {
51+
return new AnonymousProjectBuilder();
52+
}
53+
54+
public AnonymousProjectBuilder withId(String id) {
55+
this.id = id;
56+
return this;
57+
}
58+
59+
public AnonymousProjectBuilder withName(String name) {
60+
this.name = name;
61+
return this;
62+
}
63+
64+
public AnonymousProjectBuilder withOrganizationId(String organizationId) {
65+
this.organizationId = organizationId;
66+
return this;
67+
}
68+
69+
public AnonymousProjectBuilder withLanguage(String language) {
70+
this.language = language;
71+
return this;
72+
}
73+
74+
public AnonymousProjectBuilder withArchived(boolean archived) {
75+
this.archived = archived;
76+
return this;
77+
}
78+
79+
public AnonymousProjectBuilder withCritical(int critical) {
80+
this.critical = critical;
81+
return this;
82+
}
83+
84+
public AnonymousProjectBuilder withHigh(int high) {
85+
this.high = high;
86+
return this;
87+
}
88+
89+
public AnonymousProjectBuilder withMedium(int medium) {
90+
this.medium = medium;
91+
return this;
92+
}
93+
94+
public AnonymousProjectBuilder withLow(int low) {
95+
this.low = low;
96+
return this;
97+
}
98+
99+
public AnonymousProjectBuilder withNote(int note) {
100+
this.note = note;
101+
return this;
102+
}
103+
104+
public AnonymousProjectBuilder withLastScanId(String lastScanId) {
105+
this.lastScanId = lastScanId;
106+
return this;
107+
}
108+
109+
public AnonymousProjectBuilder withLastScanTime(Instant lastScanTime) {
110+
this.lastScanTime = lastScanTime;
111+
return this;
112+
}
113+
114+
public AnonymousProjectBuilder withCompletedScans(int completedScans) {
115+
this.completedScans = completedScans;
116+
return this;
117+
}
118+
119+
public AnonymousProjectBuilder withIncludeNamespaceFilters(
120+
Collection<String> includeNamespaceFilters) {
121+
this.includeNamespaceFilters = includeNamespaceFilters;
122+
return this;
123+
}
124+
125+
public AnonymousProjectBuilder withExcludeNamespaceFilters(
126+
Collection<String> excludeNamespaceFilters) {
127+
this.excludeNamespaceFilters = excludeNamespaceFilters;
128+
return this;
129+
}
130+
131+
/**
132+
* Build the Project mock with all configured values. Uses lenient stubbing to avoid
133+
* UnnecessaryStubbingException for fields not accessed in specific tests.
134+
*/
135+
public Project build() {
136+
lenient().when(project.id()).thenReturn(id);
137+
lenient().when(project.name()).thenReturn(name);
138+
lenient().when(project.organizationId()).thenReturn(organizationId);
139+
lenient().when(project.language()).thenReturn(language);
140+
lenient().when(project.archived()).thenReturn(archived);
141+
lenient().when(project.critical()).thenReturn(critical);
142+
lenient().when(project.high()).thenReturn(high);
143+
lenient().when(project.medium()).thenReturn(medium);
144+
lenient().when(project.low()).thenReturn(low);
145+
lenient().when(project.note()).thenReturn(note);
146+
lenient().when(project.lastScanId()).thenReturn(lastScanId);
147+
lenient().when(project.lastScanTime()).thenReturn(lastScanTime);
148+
lenient().when(project.completedScans()).thenReturn(completedScans);
149+
lenient().when(project.includeNamespaceFilters()).thenReturn(includeNamespaceFilters);
150+
lenient().when(project.excludeNamespaceFilters()).thenReturn(excludeNamespaceFilters);
151+
return project;
152+
}
153+
}

0 commit comments

Comments
 (0)