1+ package no.ssb.kostra.validation.report
2+
3+ import com.fasterxml.jackson.databind.SerializationFeature
4+ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
5+ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
6+ import com.fasterxml.jackson.module.kotlin.readValue
7+ import io.kotest.core.spec.style.BehaviorSpec
8+ import io.kotest.data.forAll
9+ import io.kotest.data.row
10+ import io.kotest.matchers.shouldBe
11+ import io.kotest.matchers.string.shouldContain
12+ import no.ssb.kostra.felles.git.GitProperties
13+ import no.ssb.kostra.program.KotlinArguments
14+ import java.time.LocalDateTime
15+
16+ class JSONValidationReportTest : BehaviorSpec ({
17+ val mapper = jacksonObjectMapper()
18+ .registerModule(JavaTimeModule ())
19+ .disable(SerializationFeature .WRITE_DATES_AS_TIMESTAMPS )
20+
21+ Given ("StructuredValidationReport with no report entries") {
22+ When ("generating JSON report") {
23+ val startTime = LocalDateTime .of(2023, 1, 1, 10, 0, 0)
24+ val validationReportArguments = ValidationReportArguments (
25+ kotlinArguments = KotlinArguments (
26+ skjema = "0A",
27+ aargang = "2023",
28+ region = "123456",
29+ navn = "Test Municipality ",
30+ startTime = startTime,
31+ ),
32+ validationResult = ValidationResult (
33+ reportEntries = emptyList(),
34+ numberOfControls = 5
35+ )
36+ )
37+
38+ val sut = StructuredValidationReport (
39+ validationReportArguments = validationReportArguments,
40+ gitProperties = GitProperties ("v1.0.0")
41+ )
42+
43+ val result = sut.toString()
44+
45+ Then ("JSON should contain all required fields") {
46+ result shouldContain " validationSummary"
47+ result shouldContain " reportEntries"
48+ result shouldContain " region"
49+ result shouldContain " name"
50+ result shouldContain " scheme"
51+ result shouldContain " year"
52+ result shouldContain " numberOfControls"
53+ result shouldContain " programVersion"
54+ result shouldContain " reportStartedAt"
55+ result shouldContain " reportCompletedAt"
56+ result shouldContain " reportGenerationTime"
57+ result shouldContain " severity"
58+ }
59+
60+ Then ("JSON should have correct values") {
61+ result shouldContain " \" region\" : \" 123456\" "
62+ result shouldContain " \" name\" : \" Test Municipality\" "
63+ result shouldContain " \" scheme\" : \" 0A\" "
64+ result shouldContain " \" year\" : \" 2023\" "
65+ result shouldContain " \" numberOfControls\" : 5"
66+ result shouldContain " \" programVersion\" : \" v1.0.0\" "
67+ result shouldContain " \" numFatal\" : 0"
68+ result shouldContain " \" numErrors\" : 0"
69+ result shouldContain " \" numWarnings\" : 0"
70+ result shouldContain " \" numInfo\" : 0"
71+ result shouldContain " \" numOk\" : 0"
72+ result shouldContain " \" severity\" : 0"
73+ }
74+
75+ Then ("JSON should be parseable") {
76+ val parsed = mapper.readValue<StructuredValidationContainer >(result)
77+ parsed.validationSummary.region shouldBe " 123456"
78+ parsed.validationSummary.name shouldBe " Test Municipality"
79+ parsed.validationSummary.scheme shouldBe " 0A"
80+ parsed.validationSummary.year shouldBe " 2023"
81+ parsed.validationSummary.numberOfControls shouldBe 5
82+ parsed.validationSummary.programVersion shouldBe " v1.0.0"
83+ parsed.reportEntries shouldBe emptyList()
84+ }
85+ }
86+ }
87+
88+ Given ("StructuredValidationReport with entries of different severities") {
89+ forAll(
90+ row(
91+ "FATAL entries",
92+ listOf(
93+ ValidationReportEntry (severity = Severity .FATAL , ruleName = "Rule1 ", messageText = "Fatal error"),
94+ ValidationReportEntry (severity = Severity .FATAL , ruleName = "Rule2 ", messageText = "Another fatal error")
95+ ),
96+ 2, 0, 0, 0, 0, 2
97+ ),
98+ row(
99+ "ERROR entries",
100+ listOf(
101+ ValidationReportEntry (severity = Severity .ERROR , ruleName = "Rule1 ", messageText = "Error "),
102+ ValidationReportEntry (severity = Severity .ERROR , ruleName = "Rule2 ", messageText = "Another error"),
103+ ValidationReportEntry (severity = Severity .ERROR , ruleName = "Rule3 ", messageText = "Yet another error")
104+ ),
105+ 0, 3, 0, 0, 0, 2
106+ ),
107+ row(
108+ "WARNING entries",
109+ listOf(
110+ ValidationReportEntry (severity = Severity .WARNING , ruleName = "Rule1 ", messageText = "Warning ")
111+ ),
112+ 0, 0, 1, 0, 0, 1
113+ ),
114+ row(
115+ "INFO entries",
116+ listOf(
117+ ValidationReportEntry (severity = Severity .INFO , ruleName = "Rule1 ", messageText = "Info "),
118+ ValidationReportEntry (severity = Severity .INFO , ruleName = "Rule2 ", messageText = "Another info")
119+ ),
120+ 0, 0, 0, 2, 0, 0
121+ ),
122+ row(
123+ "OK entries",
124+ listOf(
125+ ValidationReportEntry (severity = Severity .OK , ruleName = "Rule1 ", messageText = "OK ")
126+ ),
127+ 0, 0, 0, 0, 1, 0
128+ ),
129+ row(
130+ "Mixed severity entries",
131+ listOf(
132+ ValidationReportEntry (severity = Severity .FATAL , ruleName = "Rule1 ", messageText = "Fatal "),
133+ ValidationReportEntry (severity = Severity .ERROR , ruleName = "Rule2 ", messageText = "Error "),
134+ ValidationReportEntry (severity = Severity .WARNING , ruleName = "Rule3 ", messageText = "Warning "),
135+ ValidationReportEntry (severity = Severity .INFO , ruleName = "Rule4 ", messageText = "Info "),
136+ ValidationReportEntry (severity = Severity .OK , ruleName = "Rule5 ", messageText = "OK ")
137+ ),
138+ 1, 1, 1, 1, 1, 2
139+ )
140+ ) { description, reportEntries, expectedFatal, expectedError, expectedWarning, expectedInfo, expectedOk, expectedSeverity ->
141+ When (description) {
142+ val validationReportArguments = ValidationReportArguments (
143+ kotlinArguments = KotlinArguments (
144+ skjema = "0A",
145+ aargang = "2023",
146+ region = "123456",
147+ startTime = LocalDateTime .of(2023, 1, 1, 10, 0, 0)
148+ ),
149+ validationResult = ValidationResult (
150+ reportEntries = reportEntries,
151+ numberOfControls = 10
152+ )
153+ )
154+
155+ val sut = StructuredValidationReport (
156+ validationReportArguments = validationReportArguments,
157+ gitProperties = GitProperties ("v1.0.0")
158+ )
159+
160+ val result = sut.toString()
161+
162+ Then ("severity counts should be correct") {
163+ val parsed = mapper.readValue<StructuredValidationContainer >(result)
164+ parsed.validationSummary.numFatal shouldBe expectedFatal
165+ parsed.validationSummary.numErrors shouldBe expectedError
166+ parsed.validationSummary.numWarnings shouldBe expectedWarning
167+ parsed.validationSummary.numInfo shouldBe expectedInfo
168+ parsed.validationSummary.numOk shouldBe expectedOk
169+ parsed.validationSummary.severity shouldBe expectedSeverity
170+ parsed.reportEntries shouldBe reportEntries
171+ }
172+ }
173+ }
174+ }
175+
176+ Given ("StructuredValidationReport with complex report entries") {
177+ When ("entries have all fields populated") {
178+ val reportEntries = listOf(
179+ ValidationReportEntry (
180+ severity = Severity .ERROR ,
181+ caseworker = "worker1",
182+ journalId = "journal123",
183+ individId = "indiv456",
184+ contextId = "context789",
185+ ruleName = "Rule ABC ",
186+ messageText = "Detailed error message",
187+ lineNumbers = listOf(1, 2, 3)
188+ ),
189+ ValidationReportEntry (
190+ severity = Severity .WARNING ,
191+ caseworker = "worker2",
192+ journalId = "journal456",
193+ individId = "indiv789",
194+ contextId = "context012",
195+ ruleName = "Rule XYZ ",
196+ messageText = "Warning message",
197+ lineNumbers = listOf(5, 10, 15)
198+ )
199+ )
200+
201+ val validationReportArguments = ValidationReportArguments (
202+ kotlinArguments = KotlinArguments (
203+ skjema = "0B",
204+ aargang = "2024",
205+ region = "654321",
206+ navn = "Another Municipality ",
207+ startTime = LocalDateTime .of(2024, 6, 15, 14, 30, 0)
208+ ),
209+ validationResult = ValidationResult (
210+ reportEntries = reportEntries,
211+ numberOfControls = 20
212+ )
213+ )
214+
215+ val sut = StructuredValidationReport (
216+ validationReportArguments = validationReportArguments,
217+ gitProperties = GitProperties ("v2.0.0")
218+ )
219+
220+ val result = sut.toString()
221+
222+ Then ("all report entry fields should be preserved in JSON ") {
223+ val parsed = mapper.readValue<StructuredValidationContainer >(result)
224+ parsed.reportEntries.size shouldBe 2
225+
226+ val firstEntry = parsed.reportEntries[0 ]
227+ firstEntry.severity shouldBe Severity .ERROR
228+ firstEntry.caseworker shouldBe " worker1"
229+ firstEntry.journalId shouldBe " journal123"
230+ firstEntry.individId shouldBe " indiv456"
231+ firstEntry.contextId shouldBe " context789"
232+ firstEntry.ruleName shouldBe " Rule ABC"
233+ firstEntry.messageText shouldBe " Detailed error message"
234+ firstEntry.lineNumbers shouldBe listOf(1, 2, 3)
235+
236+ val secondEntry = parsed.reportEntries[1 ]
237+ secondEntry.severity shouldBe Severity .WARNING
238+ secondEntry.caseworker shouldBe " worker2"
239+ secondEntry.journalId shouldBe " journal456"
240+ }
241+ }
242+ }
243+
244+ Given ("StructuredValidationReport timestamp and duration handling") {
245+ When ("generating report with specific timestamps") {
246+ val startTime = LocalDateTime .of(2023, 3, 15, 9, 30, 0)
247+
248+ val validationReportArguments = ValidationReportArguments (
249+ kotlinArguments = KotlinArguments (
250+ skjema = "0A",
251+ aargang = "2023",
252+ region = "123456",
253+ startTime = startTime
254+ ),
255+ validationResult = ValidationResult (
256+ reportEntries = emptyList(),
257+ numberOfControls = 1
258+ )
259+ )
260+
261+ val sut = StructuredValidationReport (
262+ validationReportArguments = validationReportArguments,
263+ gitProperties = GitProperties ("v1.0.0")
264+ )
265+
266+ val result = sut.toString()
267+
268+ Then ("timestamps should be formatted correctly") {
269+ result shouldContain " reportStartedAt"
270+ result shouldContain " reportCompletedAt"
271+ result shouldContain " reportGenerationTime"
272+
273+ val parsed = mapper.readValue<StructuredValidationContainer >(result)
274+ parsed.validationSummary.reportStartedAt shouldBe startTime
275+ }
276+ }
277+ }
278+
279+ Given ("StructuredValidationReport with default git properties") {
280+ When ("no custom git properties provided") {
281+ val validationReportArguments = ValidationReportArguments (
282+ kotlinArguments = KotlinArguments (
283+ skjema = "0A",
284+ aargang = "2023",
285+ region = "123456",
286+ startTime = LocalDateTime .of(2023, 1, 1, 10, 0, 0)
287+ ),
288+ validationResult = ValidationResult (
289+ reportEntries = emptyList(),
290+ numberOfControls = 1
291+ )
292+ )
293+
294+ val sut = StructuredValidationReport (
295+ validationReportArguments = validationReportArguments
296+ )
297+
298+ val result = sut.toString()
299+
300+ Then ("report should be generated successfully") {
301+ result shouldContain " validationSummary"
302+ result shouldContain " programVersion"
303+ }
304+ }
305+ }
306+ })
0 commit comments