diff --git a/src/main/java/org/wise/portal/service/ping/CRaterPingService.java b/src/main/java/org/wise/portal/service/ping/CRaterPingService.java new file mode 100644 index 000000000..38e16deb3 --- /dev/null +++ b/src/main/java/org/wise/portal/service/ping/CRaterPingService.java @@ -0,0 +1,6 @@ +package org.wise.portal.service.ping; + +public interface CRaterPingService { + public boolean hasPingedItem(String itemId); + public void cachePingedItem(String itemId, int ttl); +} diff --git a/src/main/java/org/wise/portal/service/ping/impl/CRaterPingServiceImpl.java b/src/main/java/org/wise/portal/service/ping/impl/CRaterPingServiceImpl.java new file mode 100644 index 000000000..03a780ee7 --- /dev/null +++ b/src/main/java/org/wise/portal/service/ping/impl/CRaterPingServiceImpl.java @@ -0,0 +1,23 @@ +package org.wise.portal.service.ping.impl; + +import java.time.Duration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.wise.portal.service.ping.CRaterPingService; + +@Service +public class CRaterPingServiceImpl implements CRaterPingService { + @Autowired + private StringRedisTemplate stringRedisTemplate; + + public boolean hasPingedItem(String itemId) { + return stringRedisTemplate.opsForValue().size(itemId) == 1; + } + + public void cachePingedItem(String itemId, int ttl) { + this.stringRedisTemplate.opsForValue().setIfAbsent(itemId, "pinged"); + this.stringRedisTemplate.expire(itemId, Duration.ofSeconds(ttl)); + } +} diff --git a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterPingRequest.java b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterPingRequest.java new file mode 100644 index 000000000..12ef72f19 --- /dev/null +++ b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterPingRequest.java @@ -0,0 +1,24 @@ +package org.wise.vle.domain.webservice.crater; + +import org.json.JSONException; +import org.json.JSONObject; + +import lombok.Setter; + +@Setter +public class CRaterPingRequest extends AbstractCRaterRequest { + public String generateBodyData() throws JSONException { + JSONObject body = new JSONObject(super.generateBodyData()); + body.put("service", "LoadService"); + return body.toString(); + } + + @Override + public String getCRaterUrlVariableBase() { + return "cRater_scoring_url"; + } + + public String getItemId() { + return this.itemId; + } +} diff --git a/src/main/java/org/wise/vle/web/CRaterController.java b/src/main/java/org/wise/vle/web/CRaterController.java index 58573b402..075a095ac 100644 --- a/src/main/java/org/wise/vle/web/CRaterController.java +++ b/src/main/java/org/wise/vle/web/CRaterController.java @@ -31,6 +31,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.wise.vle.domain.webservice.crater.CRaterVerificationRequest; +import org.wise.portal.service.ping.CRaterPingService; +import org.wise.vle.domain.webservice.crater.CRaterPingRequest; import org.wise.vle.domain.webservice.crater.CRaterScoringRequest; import org.wise.vle.domain.webservice.crater.CRaterService; @@ -41,6 +43,9 @@ public class CRaterController { @Autowired private CRaterService cRaterService; + @Autowired + private CRaterPingService cRaterPingService; + @GetMapping("/verify") String verifyItemId(CRaterVerificationRequest request) throws JSONException { return cRaterService.getCRaterResponse(request); @@ -50,4 +55,14 @@ String verifyItemId(CRaterVerificationRequest request) throws JSONException { String scoreItem(@RequestBody CRaterScoringRequest request) throws JSONException { return cRaterService.getCRaterResponse(request); } + + @PostMapping("/ping") + public String pingItem(@RequestBody CRaterPingRequest ping) throws JSONException { + String itemId = ping.getItemId(); + if (!this.cRaterPingService.hasPingedItem(itemId)) { + this.cRaterPingService.cachePingedItem(itemId, 280); + return this.cRaterService.getCRaterResponse(ping); + } + return ""; + } } diff --git a/src/test/java/org/wise/portal/service/ping/impl/CRaterPingServiceImplTest.java b/src/test/java/org/wise/portal/service/ping/impl/CRaterPingServiceImplTest.java new file mode 100644 index 000000000..c4c545a98 --- /dev/null +++ b/src/test/java/org/wise/portal/service/ping/impl/CRaterPingServiceImplTest.java @@ -0,0 +1,61 @@ +package org.wise.portal.service.ping.impl; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.Duration; +import org.easymock.EasyMockExtension; +import org.easymock.Mock; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +@ExtendWith(EasyMockExtension.class) +public class CRaterPingServiceImplTest { + @TestSubject + private CRaterPingServiceImpl cRaterPingServiceImpl = new CRaterPingServiceImpl(); + + @Mock + private StringRedisTemplate stringRedisTemplate; + + @Mock + private ValueOperations valueOperations; + + private String testId = "test"; + + @Test + public void hasPingedItem_ItemPinged_ShouldReturnTrue() { + expect(stringRedisTemplate.opsForValue()).andReturn(valueOperations); + expect(valueOperations.size(testId)).andReturn(1L); + replay(stringRedisTemplate, valueOperations); + assertTrue(cRaterPingServiceImpl.hasPingedItem(testId)); + verify(stringRedisTemplate); + verify(valueOperations); + } + + @Test + public void hasPingedItem_ItemNotPinged_ShouldReturnFalse() { + expect(stringRedisTemplate.opsForValue()).andReturn(valueOperations); + expect(valueOperations.size(testId)).andReturn(0L); + replay(stringRedisTemplate, valueOperations); + assertFalse(cRaterPingServiceImpl.hasPingedItem(testId)); + verify(stringRedisTemplate); + verify(valueOperations); + + } + + @Test + public void cachePingedItem_ShouldCacheAndExpireItemId() { + expect(stringRedisTemplate.opsForValue()).andReturn(valueOperations); + expect(stringRedisTemplate.expire(testId, Duration.ofSeconds(1))).andReturn(null); + expect(valueOperations.setIfAbsent(testId, "pinged")).andReturn(null); + replay(stringRedisTemplate, valueOperations); + cRaterPingServiceImpl.cachePingedItem(testId, 1); + verify(stringRedisTemplate, valueOperations); + } +} diff --git a/src/test/java/org/wise/vle/web/CRaterControllerTest.java b/src/test/java/org/wise/vle/web/CRaterControllerTest.java index 837c018b7..d739d7887 100644 --- a/src/test/java/org/wise/vle/web/CRaterControllerTest.java +++ b/src/test/java/org/wise/vle/web/CRaterControllerTest.java @@ -1,6 +1,8 @@ package org.wise.vle.web; +import static junit.framework.TestCase.assertEquals; import static org.easymock.EasyMock.*; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import org.easymock.EasyMockExtension; @@ -9,6 +11,8 @@ import org.json.JSONException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.wise.portal.service.ping.CRaterPingService; +import org.wise.vle.domain.webservice.crater.CRaterPingRequest; import org.wise.vle.domain.webservice.crater.CRaterScoringRequest; import org.wise.vle.domain.webservice.crater.CRaterService; import org.wise.vle.domain.webservice.crater.CRaterVerificationRequest; @@ -22,8 +26,12 @@ public class CRaterControllerTest { @Mock private CRaterService cRaterService; + @Mock + private CRaterPingService cRaterPingService; + private String clientId = "wise-test"; private String itemId = "test-item-id"; + private String berkeleyItemId = "berkeley_test-item-id"; private Long trackingId = 123456789L; @Test @@ -80,4 +88,51 @@ private String createScoringResponseString(String itemId, Long trackingId, Strin responseBuffer.append("}"); return responseBuffer.toString(); } + + @Test + public void pingItem_ItemAlreadyPinged_ShouldReturnEmpty() { + CRaterPingRequest request = new CRaterPingRequest(); + request.setItemId(berkeleyItemId); + try { + expect(cRaterPingService.hasPingedItem(berkeleyItemId)).andReturn(true); + replay(cRaterService, cRaterPingService); + String response = controller.pingItem(request); + assertEquals(response, ""); + verify(cRaterService, cRaterPingService); + } catch (JSONException exception) { + + } + } + + @Test + public void pingItem_ItemNotPinged_ShouldReturnString() { + CRaterPingRequest request = new CRaterPingRequest(); + request.setItemId(berkeleyItemId); + try { + expect(cRaterService.getCRaterResponse(request)) + .andReturn(createPingResponseString(berkeleyItemId, trackingId, clientId)); + expect(cRaterPingService.hasPingedItem(berkeleyItemId)).andReturn(false); + cRaterPingService.cachePingedItem(berkeleyItemId, 280); + expectLastCall(); + replay(cRaterService, cRaterPingService); + String response = controller.pingItem(request); + assertNotNull(response); + assertNotEquals(response, ""); + verify(cRaterService, cRaterPingService); + } catch (JSONException exception) { + + } + + } + + private String createPingResponseString(String itemId, Long trackingId, String clientId) { + StringBuffer responseBuffer = new StringBuffer(); + responseBuffer.append("{"); + responseBuffer.append(" \"item_id\": \"" + itemId + "\","); + responseBuffer.append(" \"success\": true,"); + responseBuffer.append(" \"tracking_id\": " + trackingId + ","); + responseBuffer.append(" \"client_id\": \"" + clientId + "\""); + responseBuffer.append("}"); + return responseBuffer.toString(); + } }