Skip to content

Commit bac380a

Browse files
committed
refactor(test): split neutral API value tests by owning contract
1 parent f14546b commit bac380a

23 files changed

Lines changed: 1035 additions & 962 deletions

‎jsonapi-java-api/src/test/groovy/com/kazforge/jsonapi/JacksonCommonContractsSpec.groovy‎

Lines changed: 0 additions & 958 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.kazforge.jsonapi.diagnostic
2+
3+
import com.kazforge.jsonapi.core.validation.ValidationRuleCode
4+
import spock.lang.Specification
5+
6+
class JsonApiDocumentReadExceptionSpec extends Specification {
7+
8+
def "read exception carries category, pointer, location, and rule code"() {
9+
given:
10+
def location = new SourceLocation(1, 2, 3L, 4L)
11+
12+
when:
13+
def e = new JsonApiDocumentReadException(
14+
CodecFailureCategory.MALFORMED_JSON, "/data", location, "message")
15+
16+
then:
17+
e.category() == CodecFailureCategory.MALFORMED_JSON
18+
e.jsonPointer() == "/data"
19+
e.sourceLocation() == location
20+
e.ruleCode() == null
21+
e.message == "message"
22+
}
23+
24+
def "read exceptions retain their stable context and optional causes"() {
25+
given:
26+
def readCause = new IllegalArgumentException("read cause")
27+
def sourceLocation = new SourceLocation(1, 2, 3L, 4L)
28+
29+
when:
30+
def read = new JsonApiDocumentReadException(
31+
CodecFailureCategory.LOCAL_VALIDATION,
32+
"/data",
33+
sourceLocation,
34+
ValidationRuleCode.MISSING_RESOURCE_ID,
35+
"read",
36+
readCause)
37+
38+
then:
39+
read.category() == CodecFailureCategory.LOCAL_VALIDATION
40+
read.jsonPointer() == "/data"
41+
read.sourceLocation() == sourceLocation
42+
read.ruleCode() == ValidationRuleCode.MISSING_RESOURCE_ID
43+
read.getCause().is(readCause)
44+
}
45+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.kazforge.jsonapi.diagnostic
2+
3+
import spock.lang.Specification
4+
5+
class JsonApiMappingExceptionSpec extends Specification {
6+
7+
def "mapping exception carries stable diagnostic, class, and pointer-form location"() {
8+
when:
9+
def e = new JsonApiMappingException(
10+
MappingDiagnostic.MISSING_IDENTIFIER,
11+
String,
12+
MappingLocation.of("id"),
13+
"message")
14+
15+
then:
16+
e.diagnostic() == MappingDiagnostic.MISSING_IDENTIFIER
17+
e.resourceClass() == String
18+
e.location() == MappingLocation.parse("/id")
19+
e.propertyPath() == "/id"
20+
e.message == "message"
21+
}
22+
23+
def "mapping exception represents absent location as null, never empty or root"() {
24+
when:
25+
def e = JsonApiMappingException.withoutLocation(
26+
MappingDiagnostic.MISSING_RESOURCE_ANNOTATION, String, "message")
27+
28+
then:
29+
e.location() == null
30+
e.propertyPath() == null
31+
e.resourceClass() == String
32+
}
33+
34+
def "mapping exceptions retain their stable context and optional causes"() {
35+
given:
36+
def mappingCause = new IllegalStateException("mapping cause")
37+
def location = MappingLocation.of("attributes", "title")
38+
39+
when:
40+
def mapping = new JsonApiMappingException(
41+
MappingDiagnostic.UNSUPPORTED_ATTRIBUTE_VALUE, String, location, "mapping", mappingCause)
42+
def defaultMessage = new JsonApiMappingException(
43+
MappingDiagnostic.MISSING_IDENTIFIER, String, location)
44+
45+
then:
46+
mapping.diagnostic() == MappingDiagnostic.UNSUPPORTED_ATTRIBUTE_VALUE
47+
mapping.resourceClass() == String
48+
mapping.location() == location
49+
mapping.getCause().is(mappingCause)
50+
defaultMessage.message == "MISSING_IDENTIFIER"
51+
}
52+
}

jsonapi-java-api/src/test/groovy/com/kazforge/jsonapi/MappingLocationSpec.groovy renamed to jsonapi-java-api/src/test/groovy/com/kazforge/jsonapi/diagnostic/MappingLocationSpec.groovy

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
package com.kazforge.jsonapi
1+
package com.kazforge.jsonapi.diagnostic
22

3-
import com.kazforge.jsonapi.diagnostic.JsonApiMappingException
4-
import com.kazforge.jsonapi.diagnostic.MappingDiagnostic
5-
import com.kazforge.jsonapi.diagnostic.MappingLocation
63
import spock.lang.Specification
74

85
/** Escaping, parsing, and composition invariants of the mapping-diagnostic location type. */
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.kazforge.jsonapi.diagnostic
2+
3+
import spock.lang.Specification
4+
5+
class SourceLocationSpec extends Specification {
6+
7+
def "source location distinguishes known and unknown positions"() {
8+
expect:
9+
!SourceLocation.UNKNOWN.isKnown()
10+
new SourceLocation(0, 0, 0L, 0L).isKnown()
11+
new SourceLocation(-1, 2, -1L, -1L).isKnown()
12+
}
13+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.kazforge.jsonapi.document
2+
3+
import com.kazforge.jsonapi.core.model.JsonApiObject
4+
import com.kazforge.jsonapi.core.model.Links
5+
import com.kazforge.jsonapi.core.model.Meta
6+
import spock.lang.Specification
7+
8+
class DocumentEnvelopeSpec extends Specification {
9+
10+
def "document envelope preserves independent absence and present document members"() {
11+
given:
12+
def links = Links.empty()
13+
def meta = Meta.empty()
14+
def jsonapi = JsonApiObject.ofVersion("1.1")
15+
16+
expect:
17+
new DocumentEnvelope(null, null, null) == new DocumentEnvelope(null, null, null)
18+
new DocumentEnvelope(links, null, null).links() == links
19+
new DocumentEnvelope(null, meta, null).meta() == meta
20+
new DocumentEnvelope(null, null, jsonapi).jsonapi() == jsonapi
21+
new DocumentEnvelope(links, meta, jsonapi) != new DocumentEnvelope(null, meta, jsonapi)
22+
}
23+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.kazforge.jsonapi.document
2+
3+
import com.kazforge.jsonapi.core.aggregate.ValidationContext
4+
import spock.lang.Specification
5+
6+
class DocumentReadContextSpec extends Specification {
7+
8+
def "read context defaults and derivations"() {
9+
expect:
10+
DocumentReadContext.resourceDefaults().primaryDataKind() == PrimaryDataKind.RESOURCE
11+
DocumentReadContext.identifierDefaults().primaryDataKind() == PrimaryDataKind.RESOURCE_IDENTIFIER
12+
DocumentReadContext.of(ValidationContext.defaults(), PrimaryDataKind.RESOURCE) ==
13+
DocumentReadContext.resourceDefaults()
14+
DocumentReadContext.resourceDefaults()
15+
.withPrimaryDataKind(PrimaryDataKind.RESOURCE_IDENTIFIER) ==
16+
DocumentReadContext.identifierDefaults()
17+
}
18+
19+
def "read context rejects missing policy components"() {
20+
when:
21+
new DocumentReadContext(null, PrimaryDataKind.RESOURCE)
22+
23+
then:
24+
thrown(NullPointerException)
25+
26+
when:
27+
new DocumentReadContext(ValidationContext.defaults(), null)
28+
29+
then:
30+
thrown(NullPointerException)
31+
}
32+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.kazforge.jsonapi.mapping
2+
3+
import com.kazforge.jsonapi.core.model.ResourceIdentifier
4+
import spock.lang.Specification
5+
6+
class DomainDataSpec extends Specification {
7+
8+
def "resource collection is defensively copied and unmodifiable"() {
9+
given:
10+
def source = ["x"] as List<Object>
11+
12+
when:
13+
def collection = new DomainData.ResourceCollection(source)
14+
source.add("y")
15+
16+
then:
17+
collection.resources() == ["x"]
18+
19+
when:
20+
collection.resources().add("z")
21+
then:
22+
thrown(UnsupportedOperationException)
23+
}
24+
25+
def "domain data preserves explicit null, resource, and identifier primary-data states"() {
26+
given:
27+
def resource = "article dto"
28+
def identifier = ResourceIdentifier.of("articles", "1")
29+
def resources = new ArrayList<Object>([resource])
30+
def identifiers = new ArrayList<ResourceIdentifier>([identifier])
31+
32+
when:
33+
def collection = new DomainData.ResourceCollection(resources)
34+
def identifierCollection = new DomainData.IdentifierCollection(identifiers)
35+
resources.add("another dto")
36+
identifiers.add(ResourceIdentifier.of("articles", "2"))
37+
38+
then:
39+
(Set) DomainData.class.getPermittedSubclasses().toSet() ==
40+
[
41+
DomainData.NullData,
42+
DomainData.SingleResource,
43+
DomainData.ResourceCollection,
44+
DomainData.SingleIdentifier,
45+
DomainData.IdentifierCollection
46+
].toSet()
47+
DomainData.NullData.INSTANCE == new DomainData.NullData()
48+
new DomainData.SingleResource(resource).resource().is(resource)
49+
collection.resources() == [resource]
50+
new DomainData.SingleIdentifier(identifier).identifier() == identifier
51+
identifierCollection.identifiers() == [identifier]
52+
53+
when:
54+
collection.resources().add("nope")
55+
56+
then:
57+
thrown(UnsupportedOperationException)
58+
}
59+
60+
def "domain data rejects null required payloads and collection members"() {
61+
when:
62+
new DomainData.SingleResource(null)
63+
64+
then:
65+
thrown(NullPointerException)
66+
67+
when:
68+
new DomainData.ResourceCollection([null])
69+
70+
then:
71+
thrown(NullPointerException)
72+
73+
when:
74+
new DomainData.SingleIdentifier(null)
75+
76+
then:
77+
thrown(NullPointerException)
78+
79+
when:
80+
new DomainData.IdentifierCollection([null])
81+
82+
then:
83+
thrown(NullPointerException)
84+
}
85+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.kazforge.jsonapi.mapping
2+
3+
import spock.lang.Specification
4+
5+
class IdentifierConverterSpec extends Specification {
6+
7+
def "defaults converter delegates to toString and parse returns the wire string"() {
8+
given:
9+
def converter = IdentifierConverter.defaults()
10+
11+
expect:
12+
converter.convert(42L) == "42"
13+
converter.convert(null) == null
14+
converter.parse("9") == "9"
15+
converter.parse(null) == null
16+
}
17+
}

0 commit comments

Comments
 (0)