From 373c3d221ce8e38c2400fc7c073c747770b03d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chirose=E2=80=9D?= Date: Sun, 26 Oct 2025 12:09:30 +0900 Subject: [PATCH 1/6] updateEntity (initial ver.) added --- src/main/java/city/makeour/moc/MocClient.java | 51 ++++++++++++++++ .../java/city/makeour/moc/MocClientTest.java | 61 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index c9b7681..f768de7 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -2,12 +2,15 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Map; +import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient.ResponseSpec; import city.makeour.moc.ngsiv2.Ngsiv2Client; import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiClient; +import city.makeour.ngsi.v2.model.UpdateExistingEntityAttributesRequest; public class MocClient { protected Ngsiv2Client client; @@ -105,4 +108,52 @@ public ResponseSpec getEntity(String entityId, String type) { return this.entities().retrieveEntityWithResponseSpec(entityId, type, null, null, "keyValues"); } + public ResponseSpec updateEntity(String id, String type, Map attributesToUpdate) { + if (id == null || id.isBlank()) throw new IllegalArgumentException("id is required"); + if (type == null || type.isBlank()) throw new IllegalArgumentException("type is required"); + if (attributesToUpdate == null) attributesToUpdate = java.util.Collections.emptyMap(); + + try { + // Existence check + this.entities() + .retrieveEntityWithResponseSpec(id, type, null, null, "keyValues") + .toEntity(String.class); + + // Exists -> PATCH + + // 1. Create the UpdateExistingEntityAttributesRequest object + UpdateExistingEntityAttributesRequest requestBody = new UpdateExistingEntityAttributesRequest(); + + // 2. Populate the request body by manually checking keys + // (This is necessary because the Request class is not generic) + if (attributesToUpdate.containsKey("temperature")) { + requestBody.setTemperature(attributesToUpdate.get("temperature")); + } + if (attributesToUpdate.containsKey("seatNumber")) { + requestBody.setSeatNumber(attributesToUpdate.get("seatNumber")); + } + // Note: Any other keys in attributesToUpdate will be ignored. + + // 3. Pass all arguments correctly + return this.entities() + .updateExistingEntityAttributesWithResponseSpec( + id, // entityId + "application/json", // contentType + requestBody, // body + type, // type + "keyValues" // options + ); + + } catch (org.springframework.web.client.RestClientResponseException e) { + if (e.getRawStatusCode() != 404) throw e; + + // Not found -> create (This part is OK as-is) + java.util.Map body = new java.util.HashMap<>(); + body.put("id", id); + body.put("type", type); + body.putAll(attributesToUpdate); // Use the new variable name here + return this.createEntity("application/json", body); + } + } + } diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index d6fa07c..0db1b4d 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -1,17 +1,21 @@ package city.makeour.moc; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables; +import org.springframework.core.ParameterizedTypeReference; import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.model.CreateEntityRequest; @@ -146,4 +150,61 @@ void testCreateAndGetEntity_Minimal() throws GeneralSecurityException, NoSuchAlg assertNotNull(retrievedEntity); assertEquals(entityId, retrievedEntity.getId()); } + + @Test + @DisplayName("updateEntityのUpsert(作成・更新)ロジックをテストする") + @EnabledIfEnvironmentVariables({ + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_USER_POOL_ID", matches = ".*"), + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_CLIENT_ID", matches = ".*"), + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_USERNAME", matches = ".*"), + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_PASSWORD", matches = ".*") + }) + void testUpdateEntity_UpsertLogic() throws GeneralSecurityException, NoSuchAlgorithmException { + MocClient client = new MocClient(); + client.setMocAuthInfo(System.getenv("TEST_COGNITO_USER_POOL_ID"), System.getenv("TEST_COGNITO_CLIENT_ID")); + client.login(System.getenv("TEST_COGNITO_USERNAME"), System.getenv("TEST_COGNITO_PASSWORD")); + + String entityId = "urn:ngsi-ld:TestUpsert:" + UUID.randomUUID().toString(); + String entityType = "TestUpsertType"; + + // 1. "作成" (Insert) pathのテスト + // エンティティが存在しない --> catchブロックの this.createEntity + Map initialAttrs = new HashMap<>(); + initialAttrs.put("temperature", 25); + initialAttrs.put("humidity", 50); // "temperature" 以外の属性も指定 + + client.updateEntity(entityId, entityType, initialAttrs); + + // 検証 (作成) + ParameterizedTypeReference> mapType = new ParameterizedTypeReference<>() {}; + Map createdEntity = client.getEntity(entityId, entityType).body(mapType); + + assertNotNull(createdEntity); + assertEquals(entityId, createdEntity.get("id")); + assertEquals(25, createdEntity.get("temperature")); + assertEquals(50, createdEntity.get("humidity")); + + // 2. "更新" (Update/PATCH) pathのテスト + // エンティティが既に存在する --> tryブロックの updateExistingEntityAttributesWithResponseSpec + Map updateAttrs = new HashMap<>(); + updateAttrs.put("temperature", 30); // 更新 + updateAttrs.put("seatNumber", 10); // 追加 + updateAttrs.put("status", "active"); + + client.updateEntity(entityId, entityType, updateAttrs); + + // 検証 (更新) + Map updatedEntity = client.getEntity(entityId, entityType).body(mapType); + + assertNotNull(updatedEntity); + // 更新・追加されている + assertEquals(30, updatedEntity.get("temperature")); + assertEquals(10, updatedEntity.get("seatNumber")); + // 最初の作成時から変更されず残っている + assertEquals(50, updatedEntity.get("humidity")); + // PATCH処理で無視されたため存在しない + assertFalse(updatedEntity.containsKey("status")); + } + + } From 0eabf8ea113d2086429ffe21a5910572193b30a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chirose=E2=80=9D?= Date: Wed, 12 Nov 2025 10:48:22 +0900 Subject: [PATCH 2/6] updated updateEntity --- src/main/java/city/makeour/moc/MocClient.java | 44 ++++-------- .../city/makeour/moc/ngsiv2/Ngsiv2Client.java | 67 +++++++++++++++++++ 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index f768de7..3a15e26 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -118,42 +118,26 @@ public ResponseSpec updateEntity(String id, String type, Map att this.entities() .retrieveEntityWithResponseSpec(id, type, null, null, "keyValues") .toEntity(String.class); - - // Exists -> PATCH - - // 1. Create the UpdateExistingEntityAttributesRequest object - UpdateExistingEntityAttributesRequest requestBody = new UpdateExistingEntityAttributesRequest(); + + // Exists -> PATCH (keyValues 形式でそのまま送る) + return this.client.updateEntityAttributes( + id, + "application/json", + attributesToUpdate, // Object(Map) をそのまま PATCH + type, + "keyValues" + ); - // 2. Populate the request body by manually checking keys - // (This is necessary because the Request class is not generic) - if (attributesToUpdate.containsKey("temperature")) { - requestBody.setTemperature(attributesToUpdate.get("temperature")); - } - if (attributesToUpdate.containsKey("seatNumber")) { - requestBody.setSeatNumber(attributesToUpdate.get("seatNumber")); - } - // Note: Any other keys in attributesToUpdate will be ignored. - - // 3. Pass all arguments correctly - return this.entities() - .updateExistingEntityAttributesWithResponseSpec( - id, // entityId - "application/json", // contentType - requestBody, // body - type, // type - "keyValues" // options - ); - } catch (org.springframework.web.client.RestClientResponseException e) { - if (e.getRawStatusCode() != 404) throw e; - - // Not found -> create (This part is OK as-is) + if (e.getStatusCode().value() != 404) throw e; + + // Not found -> create (従来通り) java.util.Map body = new java.util.HashMap<>(); body.put("id", id); body.put("type", type); - body.putAll(attributesToUpdate); // Use the new variable name here + body.putAll(attributesToUpdate); return this.createEntity("application/json", body); - } + } } } diff --git a/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java b/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java index c9bb224..eab56e7 100644 --- a/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java +++ b/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java @@ -71,4 +71,71 @@ public RestClient.ResponseSpec createEntity(String contentType, Object body) { localVarReturnType); } } + + public RestClient.ResponseSpec updateEntityAttributes( + String entityId, + String contentType, + Object body, + String type, + String options + ) { + if (entityId == null) { + throw new RestClientResponseException( + "Missing the required parameter 'entityId' when calling updateEntityAttributes", + HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), + (HttpHeaders) null, (byte[]) null, (Charset) null + ); + } + if (contentType == null) { + throw new RestClientResponseException( + "Missing the required parameter 'contentType' when calling updateEntityAttributes", + HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), + (HttpHeaders) null, (byte[]) null, (Charset) null + ); + } + if (body == null) { + throw new RestClientResponseException( + "Missing the required parameter 'body' when calling updateEntityAttributes", + HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), + (HttpHeaders) null, (byte[]) null, (Charset) null + ); + } + + Map pathParams = new HashMap(); + pathParams.put("entityId", entityId); + + MultiValueMap queryParams = new LinkedMultiValueMap(); + HttpHeaders headerParams = new HttpHeaders(); + MultiValueMap cookieParams = new LinkedMultiValueMap(); + MultiValueMap formParams = new LinkedMultiValueMap(); + + // ?type=... と ?options=... + queryParams.putAll(this.apiClient.parameterToMultiValueMap(null, "type", type)); + queryParams.putAll(this.apiClient.parameterToMultiValueMap(null, "options", options)); + + headerParams.add("Content-Type", this.apiClient.parameterToString(contentType)); + + String[] localVarAccepts = new String[] { }; // 204 No Content 想定 + List localVarAccept = this.apiClient.selectHeaderAccept(localVarAccepts); + String[] localVarContentTypes = new String[] { "application/json" }; + MediaType localVarContentType = this.apiClient.selectHeaderContentType(localVarContentTypes); + String[] localVarAuthNames = new String[] { }; + + ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference<>() {}; + return this.apiClient.invokeAPI( + "/v2/entities/{entityId}/attrs", + HttpMethod.PATCH, + pathParams, + queryParams, + body, // ← Map 等をそのまま渡す + headerParams, + cookieParams, + formParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localVarReturnType + ); + } + } From f4cb2c53b794e633d265517d34144d84d867e3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chirose=E2=80=9D?= Date: Wed, 12 Nov 2025 21:18:58 +0900 Subject: [PATCH 3/6] changed PATCH to POST --- src/main/java/city/makeour/moc/MocClient.java | 4 ++-- src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 3a15e26..a23c1c2 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -119,7 +119,7 @@ public ResponseSpec updateEntity(String id, String type, Map att .retrieveEntityWithResponseSpec(id, type, null, null, "keyValues") .toEntity(String.class); - // Exists -> PATCH (keyValues 形式でそのまま送る) + // Exists -> POST (keyValues 形式でそのまま送る) return this.client.updateEntityAttributes( id, "application/json", @@ -127,7 +127,7 @@ public ResponseSpec updateEntity(String id, String type, Map att type, "keyValues" ); - + } catch (org.springframework.web.client.RestClientResponseException e) { if (e.getStatusCode().value() != 404) throw e; diff --git a/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java b/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java index eab56e7..2cd025e 100644 --- a/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java +++ b/src/main/java/city/makeour/moc/ngsiv2/Ngsiv2Client.java @@ -124,7 +124,7 @@ public RestClient.ResponseSpec updateEntityAttributes( ParameterizedTypeReference localVarReturnType = new ParameterizedTypeReference<>() {}; return this.apiClient.invokeAPI( "/v2/entities/{entityId}/attrs", - HttpMethod.PATCH, + HttpMethod.POST, pathParams, queryParams, body, // ← Map 等をそのまま渡す From 5df4085977d4b5f5076bdbf059824cb369dd819e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chirose=E2=80=9D?= Date: Mon, 24 Nov 2025 00:10:15 +0900 Subject: [PATCH 4/6] updated updateEntity --- src/main/java/city/makeour/moc/MocClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index b6fdedc..6baedcc 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -158,7 +158,7 @@ public ResponseSpec updateEntity(String id, String type, Map att // Existence check this.entities() .retrieveEntityWithResponseSpec(id, type, null, null, "keyValues") - .toEntity(String.class); + .toEntity(Object.class); // Exists -> POST (keyValues 形式でそのまま送る) return this.client.updateEntityAttributes( From 751452e644bc3d1fd6848cde28532792697ff08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chirose=E2=80=9D?= Date: Mon, 24 Nov 2025 00:19:55 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E5=A5=87=E6=95=B0=E6=A1=81=E3=81=AA?= =?UTF-8?q?=E3=82=89=E5=85=88=E9=A0=AD=E3=81=AB=200=20=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=ABHelper.ja?= =?UTF-8?q?va=E3=81=AB=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/city/makeour/moc/auth/srp/Helper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/city/makeour/moc/auth/srp/Helper.java b/src/main/java/city/makeour/moc/auth/srp/Helper.java index 97884dd..1db5c7a 100644 --- a/src/main/java/city/makeour/moc/auth/srp/Helper.java +++ b/src/main/java/city/makeour/moc/auth/srp/Helper.java @@ -36,6 +36,12 @@ public static byte[] padHex(BigInteger bigInt) { } public static byte[] hexStringToByteArray(String s) { + + // 奇数桁なら先頭に 0 を追加 + if (s.length() % 2 != 0) { + s = "0" + s; + } + int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { From cf33b9b529e380620851c9e591806a4922fb9aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chirose=E2=80=9D?= Date: Mon, 24 Nov 2025 00:34:01 +0900 Subject: [PATCH 6/6] changed assertFalse to assertEquals --- src/test/java/city/makeour/moc/MocClientTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index 3497b01..20149b3 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -202,7 +202,7 @@ void testUpdateEntity_UpsertLogic() throws GeneralSecurityException, NoSuchAlgor assertEquals(10, updatedEntity.get("seatNumber")); // 最初の作成時から変更されず残っている assertEquals(50, updatedEntity.get("humidity")); - // PATCH処理で無視されたため存在しない - assertFalse(updatedEntity.containsKey("status")); + + assertEquals("active", updatedEntity.get("status")); } }