Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion src/main/kotlin/com/ctrlhub/core/geo/Property.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
package com.ctrlhub.core.geo

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.github.jasminb.jsonapi.StringIdHandler
import com.github.jasminb.jsonapi.annotations.Id
import com.github.jasminb.jsonapi.annotations.Type

@Type("properties")
@JsonIgnoreProperties(ignoreUnknown = true)
data class Property(
@Id(StringIdHandler::class) val id: String? = null
@Id(StringIdHandler::class) val id: String? = null,
val address: Address? = null,
val location: Location? = null,
val meters: List<Meter>? = null,
val psr: Psr? = null,
val uprn: Long? = null
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Address(
val description: String? = null,
val department: String? = null,
val organisation: String? = null,
val number: String? = null,
val name: String? = null,
val thoroughfare: String? = null,
@JsonProperty("dependent_thoroughfare") val dependentThoroughfare: String? = null,
@JsonProperty("post_town") val postTown: String? = null,
val postcode: String? = null,
@JsonProperty("po_box") val poBox: String? = null,
val country: String? = null
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Location(
@JsonProperty("british_national_grid") val britishNationalGrid: BritishNationalGrid? = null,
@JsonProperty("lat_long") val latLong: LatLong? = null
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class BritishNationalGrid(
val easting: Int? = null,
val northing: Int? = null
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class LatLong(
val latitude: Double? = null,
val longitude: Double? = null
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Psr(
val indicator: Boolean? = null,
val priority: String? = null,
val notes: String? = null,
val contact: String? = null
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Meter(
val type: String? = null,
val number: Long? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,84 @@ class OperationsRouterTest {
assertEquals("timeband-1", appointment?.timeBand?.id)
}
}

@Test
fun `can retrieve operation with included properties`() {
val jsonFilePath = Paths.get("src/test/resources/projects/operations/all-operations-with-included-properties.json")
val jsonContent = Files.readString(jsonFilePath)

val mockEngine = MockEngine { request ->
respond(
content = jsonContent,
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}

val operationsRouter = OperationsRouter(httpClient = HttpClient(mockEngine).configureForTest())

runBlocking {
val response = operationsRouter.one(
organisationId = "123",
operationId = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
requestParameters = OperationRequestParameters(
includes = listOf(
OperationIncludes.Properties
)
)
)

assertIs<Operation>(response)
assertNotNull(response.properties, "Properties should not be null")
assertEquals(2, response.properties?.size, "Should have 2 properties")

// Validate first property
val property1 = response.properties?.find { it.id == "f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c" }
assertNotNull(property1, "First property should exist")

// Validate address details
assertNotNull(property1?.address)
assertEquals("42, Lorem Street, AB12 3CD", property1?.address?.description)
assertEquals("42", property1?.address?.number)
assertEquals("Lorem House", property1?.address?.name)
assertEquals("Lorem Street", property1?.address?.thoroughfare)
assertEquals("DOLOR", property1?.address?.postTown)
assertEquals("AB12 3CD", property1?.address?.postcode)
assertEquals("United Kingdom", property1?.address?.country)

// Validate location details
assertNotNull(property1?.location)
assertNotNull(property1?.location?.latLong)
assertEquals(51.5074, property1?.location?.latLong?.latitude)
assertEquals(-0.1278, property1?.location?.latLong?.longitude)

// Validate meters
assertNotNull(property1?.meters)
assertEquals(1, property1?.meters?.size)
assertEquals("mprn", property1?.meters?.get(0)?.type)
assertEquals(1234567890, property1?.meters?.get(0)?.number)

// Validate UPRN
assertEquals(100000000001, property1?.uprn)

// Validate PSR data
assertNotNull(property1?.psr)
assertEquals(false, property1?.psr?.indicator)

// Validate second property with PSR indicator
val property2 = response.properties?.find { it.id == "e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b" }
assertNotNull(property2, "Second property should exist")
assertEquals("15, Dolor Avenue, EF45 6GH", property2?.address?.description)
assertEquals(100000000002, property2?.uprn)

// Validate meters for property2
assertNotNull(property2?.meters)
assertEquals(1, property2?.meters?.size)
assertEquals("mprn", property2?.meters?.get(0)?.type)
assertEquals(9876543210, property2?.meters?.get(0)?.number)

// Validate PSR
assertEquals(true, property2?.psr?.indicator)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
{
"data": {
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"type": "operations",
"attributes": {
"code": "",
"dates": {
"scheduled": {
"start": "2025-08-15T07:00:00Z",
"end": "2025-08-15T11:00:00Z"
}
},
"description": "",
"done_reason": "",
"labels": [],
"name": "Lorem Ipsum Operation",
"requirements": {
"forms": [
{
"id": "f1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
"required": true
}
]
},
"status": "todo"
},
"relationships": {
"appointment": {
"data": {
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"type": "appointments"
}
},
"assignees": {
"data": [
{
"id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f",
"type": "users"
}
]
},
"forms": {
"data": [
{
"id": "f1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
"type": "forms"
}
]
},
"organisation": {
"data": {
"id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a",
"type": "organisations"
}
},
"permits": {
"data": []
},
"properties": {
"data": [
{
"id": "e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b",
"type": "properties"
},
{
"id": "f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c",
"type": "properties"
}
]
},
"scheme": {
"data": {
"id": "a7b8c9d0-e1f2-4a3b-4c5d-6e7f8a9b0c1d",
"type": "schemes"
}
},
"streets": {
"data": []
},
"teams": {
"data": []
},
"template": {
"data": null
},
"work_order": {
"data": {
"id": "b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e",
"type": "work-orders"
}
}
},
"meta": {
"created_at": "2025-08-14T09:23:43.023Z",
"updated_at": "2025-10-07T10:41:18.231Z",
"counts": {
"properties": 2,
"streets": 0
}
}
},
"jsonapi": {
"version": "1.0"
},
"included": [
{
"id": "f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c",
"type": "properties",
"attributes": {
"address": {
"description": "42, Lorem Street, AB12 3CD",
"department": "Department of Lorem",
"organisation": "Ipsum Corporation",
"number": "42",
"name": "Lorem House",
"thoroughfare": "Lorem Street",
"dependent_thoroughfare": "Ipsum Lane",
"post_town": "DOLOR",
"postcode": "AB12 3CD",
"po_box": "",
"country": "United Kingdom"
},
"location": {
"british_national_grid": null,
"lat_long": {
"latitude": 51.5074,
"longitude": -0.1278
}
},
"meters": [
{
"type": "mprn",
"number": 1234567890
}
],
"psr": {
"indicator": false,
"priority": null,
"notes": null,
"contact": null
},
"uprn": 100000000001
}
},
{
"id": "e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b",
"type": "properties",
"attributes": {
"address": {
"description": "15, Dolor Avenue, EF45 6GH",
"department": "Sit Amet Department",
"organisation": "Consectetur Limited",
"number": "15",
"name": "Amet Building",
"thoroughfare": "Dolor Avenue",
"dependent_thoroughfare": "Consectetur Road",
"post_town": "ADIPISCING",
"postcode": "EF45 6GH",
"po_box": "",
"country": "United Kingdom"
},
"location": {
"british_national_grid": null,
"lat_long": {
"latitude": 51.5145,
"longitude": -0.0945
}
},
"meters": [
{
"type": "mprn",
"number": 9876543210
}
],
"psr": {
"indicator": true,
"priority": "high",
"notes": "Lorem ipsum dolor sit amet",
"contact": "John Doe"
},
"uprn": 100000000002
}
}
]
}