From 8fc48427a0a685e84dffe741b0b7b91bac4b2e51 Mon Sep 17 00:00:00 2001 From: Johnathan James Date: Thu, 15 Aug 2024 18:49:18 -0600 Subject: [PATCH 1/2] =?UTF-8?q?GenericResponseDTOs=20now=20return=20boolea?= =?UTF-8?q?n=20rather=20than=20string=20'true'/'fal=E2=80=A6=20(#256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GenericResponseDTOs now return boolean rather than string 'true'/'false' values * Corrected a couple tests to use booleanMessage in the response dto --- .../controllers/AttributesAPIController.java | 6 ++--- .../controllers/AttributesAPITest.java | 26 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java b/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java index 515999ed..76378a2f 100644 --- a/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java +++ b/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java @@ -78,16 +78,16 @@ public ResponseEntity applyPhraseToUser(@RequestBody @Valid req.userId, req.adverb, req.verb, req.preposition, req.noun); if (isPhraseApplied) { sendNotification(true, req.userId); - GenericResponseDTO rtn = GenericResponseService.createDTO("true"); + GenericResponseDTO rtn = GenericResponseService.createDTO(true); return ResponseEntity.status(HttpStatus.OK).body(rtn); } else { sendNotification(false, req.userId); - GenericResponseDTO rtn = GenericResponseService.createDTO("false"); + GenericResponseDTO rtn = GenericResponseService.createDTO(false); return ResponseEntity.status(HttpStatus.OK).body(rtn); } } else { sendNotification(false, req.userId); - GenericResponseDTO rtn = GenericResponseService.createDTO("false"); + GenericResponseDTO rtn = GenericResponseService.createDTO(false); return ResponseEntity.status(HttpStatus.OK).body(rtn); } } diff --git a/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java b/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java index e5afb262..ff88cacd 100644 --- a/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java +++ b/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java @@ -163,19 +163,21 @@ public void applyPhraseToUserWhenPhraseValidAndApplicable() throws Exception { when(notificationService.createNotification( any(NotificationType.class), anyLong(), anyString(), anyString())) .thenReturn(null); + + final boolean boolResponse = true; when(GenericResponseService.createDTO( - anyString())) + boolResponse)) .thenReturn(GenericResponseDTO.builder() - .responseMessage("true") + .booleanMessage(boolResponse) .build()); + ArgumentCaptor notificationTypeCaptor = ArgumentCaptor.forClass(NotificationType.class); ArgumentCaptor userIdCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor notificationTypeNameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor notificationContentCaptor = ArgumentCaptor.forClass(String.class); - String template = "{\"responseMessage\": \"%s\"}"; - String expectedMessage = String.format(template, "true"); + String expectedMessage = "{\"booleanMessage\":" + true + "}"; this.mockMvc .perform( @@ -224,10 +226,11 @@ public void applyPhraseToUserWhenPhraseValidButNotApplied() throws Exception { when(notificationService.createNotification( any(NotificationType.class), anyLong(), anyString(), anyString())) .thenReturn(null); + final boolean boolResponse = false; when(GenericResponseService.createDTO( - anyString())) + boolResponse)) .thenReturn(GenericResponseDTO.builder() - .responseMessage("false") + .booleanMessage(boolResponse) .build()); ArgumentCaptor notificationTypeCaptor = ArgumentCaptor.forClass(NotificationType.class); @@ -237,8 +240,7 @@ public void applyPhraseToUserWhenPhraseValidButNotApplied() throws Exception { String notificationContent = "Your attribute was rejected. This attribute is unsuitable and cannot be applied to users."; - String template = "{\"responseMessage\": \"%s\"}"; - String expectedMessage = String.format(template, "false"); + String expectedMessage = "{\"booleanMessage\":" + false + "}"; this.mockMvc .perform( @@ -284,10 +286,11 @@ public void applyPhraseToUserWhenPhraseInvalid() throws Exception { when(notificationService.createNotification( any(NotificationType.class), anyLong(), anyString(), anyString())) .thenReturn(null); + final boolean boolResponse = false; when(GenericResponseService.createDTO( - anyString())) + boolResponse)) .thenReturn(GenericResponseDTO.builder() - .responseMessage("false") + .booleanMessage(boolResponse) .build()); ArgumentCaptor notificationTypeCaptor = ArgumentCaptor.forClass(NotificationType.class); @@ -297,8 +300,7 @@ public void applyPhraseToUserWhenPhraseInvalid() throws Exception { String notificationContent = "Your attribute was rejected. This attribute is unsuitable and cannot be applied to users."; - String template = "{\"responseMessage\": \"%s\"}"; - String expectedMessage = String.format(template, "false"); + String expectedMessage = "{\"booleanMessage\":" + false + "}"; this.mockMvc .perform( From f3e34fd509f2549c84ae20083130b335837f11a8 Mon Sep 17 00:00:00 2001 From: loretdemolas <122843014+loretdemolas@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:49:11 -0700 Subject: [PATCH 2/2] Feature/attributes page (#252) * quick commit * modified: src/main/java/com/savvato/tribeapp/entities/PhraseSequence.java -changed primary key to a composit key new file: src/main/java/com/savvato/tribeapp/entities/PhraseSequenceKey.java -added a new file to create a composite key modified: src/main/java/com/savvato/tribeapp/repositories/PhraseSequenceRepository.java -modified on duplicate to only update the position and not the phraseID. modified: src/main/resources/db/migration/changelog-202404091617.xml -changed changelog to change composite key to not include the position new file: src/main/resources/db/migration/changelog-202405071617.xml -new changelog to add sequences to the test data modified: src/main/resources/db/migration/changelog-master.xml -added new changelog to the master. * modified: src/main/resources/db/migration/changelog-202405071617.xml -added test context * modified: src/test/java/com/savvato/tribeapp/services/AttributesServiceImplTest.java -worked on tests. * src/test/java/com/savvato/tribeapp/services/AttributesServiceImplTest.java -The test is now fixed * src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java Added test for attributeAPIController * lost commit * Update AttributesAPIController.java Formatting * dto/AttributeDTO.java -added int sequence dto/PhraseDTO.java -removed int sequence services/AttributesServiceImpl.java -added logic to add squence to attriubute dto changelog-202405071617.xml added test data for missing attributes * services/AttributesServiceImplTest.java testing fixes * end of night quick commit * AttributesAPIController.java AttributesService.java AttributesServiceImpl.java * added new liquidbase script to add more attributes * Added tests for attributeDTO added changelog for adding more attributes * fixded some growing pains * Reverted changes to changelog files for test data * Deleted files that were not used deleted: src/main/java/com/savvato/tribeapp/dto/PhraseSequenceDTO.java deleted: src/main/java/com/savvato/tribeapp/dto/PhraseSequenceData.java --------- Co-authored-by: Johnathan James --- .../controllers/AttributesAPIController.java | 19 +++ .../controllers/dto/AttributesRequest.java | 11 ++ .../dto/PhraseSequenceDataRequest.java | 13 ++ .../dto/PhraseSequenceRequest.java | 17 +++ .../savvato/tribeapp/dto/AttributeDTO.java | 6 +- .../com/savvato/tribeapp/dto/PhraseDTO.java | 1 + .../tribeapp/entities/PhraseSequence.java | 57 +++++++ .../tribeapp/entities/PhraseSequenceKey.java | 48 ++++++ .../PhraseSequenceRepository.java | 27 ++++ .../tribeapp/services/AttributesService.java | 6 + .../services/AttributesServiceImpl.java | 67 +++++++-- .../db/migration/changelog-202404091617.xml | 43 ++++++ .../db/migration/changelog-202405071617.xml | 37 +++++ .../db/migration/changelog-202407301617.xml | 68 +++++++++ .../db/migration/changelog-master.xml | 3 +- .../controllers/AttributesAPITest.java | 50 ++++++- .../services/AttributesServiceImplTest.java | 141 ++++++++++++++++-- 17 files changed, 583 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceDataRequest.java create mode 100644 src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceRequest.java create mode 100644 src/main/java/com/savvato/tribeapp/entities/PhraseSequence.java create mode 100644 src/main/java/com/savvato/tribeapp/entities/PhraseSequenceKey.java create mode 100644 src/main/java/com/savvato/tribeapp/repositories/PhraseSequenceRepository.java create mode 100644 src/main/resources/db/migration/changelog-202404091617.xml create mode 100644 src/main/resources/db/migration/changelog-202405071617.xml create mode 100644 src/main/resources/db/migration/changelog-202407301617.xml diff --git a/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java b/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java index 76378a2f..5edc6924 100644 --- a/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java +++ b/src/main/java/com/savvato/tribeapp/controllers/AttributesAPIController.java @@ -5,6 +5,7 @@ import com.savvato.tribeapp.controllers.annotations.controllers.AttributesAPIController.GetAttributesForUser; import com.savvato.tribeapp.controllers.annotations.controllers.AttributesAPIController.GetUserPhrasesToBeReviewed; import com.savvato.tribeapp.controllers.dto.AttributesRequest; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceRequest; import com.savvato.tribeapp.dto.AttributeDTO; import com.savvato.tribeapp.dto.GenericResponseDTO; import com.savvato.tribeapp.dto.ToBeReviewedDTO; @@ -49,6 +50,8 @@ public class AttributesAPIController { AttributesAPIController() { } + //modify to send seq + @GetAttributesForUser @GetMapping("/{userId}") public ResponseEntity> getAttributesForUser( @@ -59,6 +62,22 @@ public ResponseEntity> getAttributesForUser( if (opt.isPresent()) return ResponseEntity.status(HttpStatus.OK).body(opt.get()); else return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); } + + + @PostMapping("/update") + public ResponseEntity uPhraseSequences(@RequestBody @Valid PhraseSequenceRequest req) { + PhraseSequenceRequest newRequest = new PhraseSequenceRequest(req.userId, req.phrases); + + // Create a request object that includes userId and the list of PhraseSequenceDataRequest + boolean success = attributesService.loadSequence(newRequest); + + if (success){ + GenericResponseDTO rtn = GenericResponseService.createDTO(true); + return ResponseEntity.status(HttpStatus.OK).body(rtn); + } else { + return ResponseEntity.status(500).build(); + } + } @GetUserPhrasesToBeReviewed @GetMapping("/in-review/{userId}") diff --git a/src/main/java/com/savvato/tribeapp/controllers/dto/AttributesRequest.java b/src/main/java/com/savvato/tribeapp/controllers/dto/AttributesRequest.java index 82ab5691..87004080 100644 --- a/src/main/java/com/savvato/tribeapp/controllers/dto/AttributesRequest.java +++ b/src/main/java/com/savvato/tribeapp/controllers/dto/AttributesRequest.java @@ -1,7 +1,15 @@ package com.savvato.tribeapp.controllers.dto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor // hold when data is coming in to the request @Schema(description = "An attributes request") public class AttributesRequest { @@ -23,4 +31,7 @@ public class AttributesRequest { @Schema(example = "UNICEF") public String noun; + + public List phrases; + } diff --git a/src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceDataRequest.java b/src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceDataRequest.java new file mode 100644 index 00000000..c97a41df --- /dev/null +++ b/src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceDataRequest.java @@ -0,0 +1,13 @@ +package com.savvato.tribeapp.controllers.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PhraseSequenceDataRequest { + public Long phraseId; + public Integer sequence; +} diff --git a/src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceRequest.java b/src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceRequest.java new file mode 100644 index 00000000..a98fe364 --- /dev/null +++ b/src/main/java/com/savvato/tribeapp/controllers/dto/PhraseSequenceRequest.java @@ -0,0 +1,17 @@ +package com.savvato.tribeapp.controllers.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PhraseSequenceRequest { + public Long userId; + public List phrases; + +} + diff --git a/src/main/java/com/savvato/tribeapp/dto/AttributeDTO.java b/src/main/java/com/savvato/tribeapp/dto/AttributeDTO.java index 20489620..5ad853a8 100644 --- a/src/main/java/com/savvato/tribeapp/dto/AttributeDTO.java +++ b/src/main/java/com/savvato/tribeapp/dto/AttributeDTO.java @@ -1,7 +1,10 @@ package com.savvato.tribeapp.dto; +import com.savvato.tribeapp.entities.PhraseSequence; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; +import lombok.*; + +import java.util.List; // hold when data is going out @@ -11,4 +14,5 @@ public class AttributeDTO { public PhraseDTO phrase; @Schema(example = "1") public Integer userCount; + public int sequence; } diff --git a/src/main/java/com/savvato/tribeapp/dto/PhraseDTO.java b/src/main/java/com/savvato/tribeapp/dto/PhraseDTO.java index 34e2c8b5..4123f6eb 100644 --- a/src/main/java/com/savvato/tribeapp/dto/PhraseDTO.java +++ b/src/main/java/com/savvato/tribeapp/dto/PhraseDTO.java @@ -21,4 +21,5 @@ public class PhraseDTO { @Schema(example = "UNICEF") public String noun; + } diff --git a/src/main/java/com/savvato/tribeapp/entities/PhraseSequence.java b/src/main/java/com/savvato/tribeapp/entities/PhraseSequence.java new file mode 100644 index 00000000..7090724e --- /dev/null +++ b/src/main/java/com/savvato/tribeapp/entities/PhraseSequence.java @@ -0,0 +1,57 @@ +package com.savvato.tribeapp.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + + +@Entity +@Table(name = "phrase_sequence") +public class PhraseSequence { + + @EmbeddedId + private PhraseSequenceKey id; + + @Column(name = "position") + private Integer position; + + public PhraseSequence() {} + + public PhraseSequence(Long userId, Long phraseId, Integer position) { + this.id = new PhraseSequenceKey(userId, phraseId); + this.position = position; + } + + public PhraseSequenceKey getId() { + return id; + } + + public void setId(PhraseSequenceKey id) { + this.id = id; + } + + public Long getUserId() { + return id.getUserId(); + } + + public void setUserId(Long userId) { + id.setUserId(userId); + } + + public Long getPhraseId() { + return id.getPhraseId(); + } + + public void setPhraseId(Long phraseId) { + id.setPhraseId(phraseId); + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } +} diff --git a/src/main/java/com/savvato/tribeapp/entities/PhraseSequenceKey.java b/src/main/java/com/savvato/tribeapp/entities/PhraseSequenceKey.java new file mode 100644 index 00000000..9d8a4082 --- /dev/null +++ b/src/main/java/com/savvato/tribeapp/entities/PhraseSequenceKey.java @@ -0,0 +1,48 @@ +package com.savvato.tribeapp.entities; + +import jakarta.persistence.Embeddable; +import java.io.Serializable; +import java.util.Objects; + +@Embeddable +public class PhraseSequenceKey implements Serializable { + private Long userId; + private Long phraseId; + + public PhraseSequenceKey() {} + + public PhraseSequenceKey(Long userId, Long phraseId) { + this.userId = userId; + this.phraseId = phraseId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getPhraseId() { + return phraseId; + } + + public void setPhraseId(Long phraseId) { + this.phraseId = phraseId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PhraseSequenceKey that = (PhraseSequenceKey) o; + return Objects.equals(userId, that.userId) && + Objects.equals(phraseId, that.phraseId); + } + + @Override + public int hashCode() { + return Objects.hash(userId, phraseId); + } +} diff --git a/src/main/java/com/savvato/tribeapp/repositories/PhraseSequenceRepository.java b/src/main/java/com/savvato/tribeapp/repositories/PhraseSequenceRepository.java new file mode 100644 index 00000000..0a904c89 --- /dev/null +++ b/src/main/java/com/savvato/tribeapp/repositories/PhraseSequenceRepository.java @@ -0,0 +1,27 @@ +package com.savvato.tribeapp.repositories; + +import com.savvato.tribeapp.entities.PhraseSequence; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Repository +public interface PhraseSequenceRepository extends JpaRepository { + @Query(nativeQuery = true, value = "SELECT * FROM phrase_sequence WHERE user_id = ?1 ORDER BY position") + List findByUserIdOrderByPosition(Long userId); + + @Modifying + @Transactional + @Query(nativeQuery = true, value = "INSERT INTO phrase_sequence (user_id, phrase_id, position) VALUES (:userId, :phraseId, :position) " + + "ON DUPLICATE KEY UPDATE position = VALUES(position)") + void storeOrUpdatePhrases(@Param("userId") Long userId, @Param("phraseId") Long phraseId, @Param("position") Integer position); + + @Query(nativeQuery = true, value = "SELECT COALESCE(MAX(position), 0) + 1 FROM phrase_sequence WHERE user_id = ?1") + Integer findNextAvailableSequence(Long userId); + +} diff --git a/src/main/java/com/savvato/tribeapp/services/AttributesService.java b/src/main/java/com/savvato/tribeapp/services/AttributesService.java index 2d255c81..9a3aa275 100644 --- a/src/main/java/com/savvato/tribeapp/services/AttributesService.java +++ b/src/main/java/com/savvato/tribeapp/services/AttributesService.java @@ -1,5 +1,7 @@ package com.savvato.tribeapp.services; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceDataRequest; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceRequest; import com.savvato.tribeapp.dto.AttributeDTO; import java.util.List; @@ -8,5 +10,9 @@ public interface AttributesService { Optional> getAttributesByUserId(Long id); + + void updatePhraseSequences(long userId, PhraseSequenceDataRequest phrase); + + boolean loadSequence(PhraseSequenceRequest phrases); } diff --git a/src/main/java/com/savvato/tribeapp/services/AttributesServiceImpl.java b/src/main/java/com/savvato/tribeapp/services/AttributesServiceImpl.java index c0c1c24a..fbc8004d 100644 --- a/src/main/java/com/savvato/tribeapp/services/AttributesServiceImpl.java +++ b/src/main/java/com/savvato/tribeapp/services/AttributesServiceImpl.java @@ -1,15 +1,18 @@ package com.savvato.tribeapp.services; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceDataRequest; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceRequest; import com.savvato.tribeapp.dto.AttributeDTO; import com.savvato.tribeapp.dto.PhraseDTO; +import com.savvato.tribeapp.entities.PhraseSequence; +import com.savvato.tribeapp.repositories.PhraseSequenceRepository; import com.savvato.tribeapp.repositories.UserPhraseRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; @Service public class AttributesServiceImpl implements AttributesService { @@ -20,9 +23,19 @@ public class AttributesServiceImpl implements AttributesService { @Autowired UserPhraseRepository userPhraseRepository; + @Autowired + private PhraseSequenceRepository phraseSequenceRepository; + @Override public Optional> getAttributesByUserId(Long userId) { + List phraseSequences = phraseSequenceRepository.findByUserIdOrderByPosition(userId); + + Map phraseIdToSequenceMap = phraseSequences.stream() + .collect(Collectors.toMap( + PhraseSequence::getPhraseId, + PhraseSequence::getPosition + )); // Get all user phrases as phraseDTOs Optional> optUserPhraseDTOs = phraseService.getPhraseInformationByUserId(userId); @@ -30,17 +43,51 @@ public Optional> getAttributesByUserId(Long userId) { // If there are phrases, build DTO and add to attributes list if (optUserPhraseDTOs.isPresent()) { Map phraseDTOUserCountMap = optUserPhraseDTOs.get(); + List attributes = phraseDTOUserCountMap .entrySet() .stream() - .map( - entry -> AttributeDTO.builder().phrase(entry.getKey()).userCount(entry.getValue()).build() - ) - .toList(); + .map(entry -> { + PhraseDTO phraseDTO = entry.getKey(); + Integer userCount = entry.getValue(); + + // Populate the sequence in PhraseDTO + int sequence= phraseIdToSequenceMap.get(phraseDTO.id); // Find sequence + List phrases = phraseSequenceRepository.findByUserIdOrderByPosition(userId); + return AttributeDTO.builder() + .phrase(phraseDTO) + .userCount(userCount) + .sequence(sequence) // No change to user count + .build(); + }) + .collect(Collectors.toList()); + attributes.sort(Comparator.comparingLong(a -> (a.phrase.id))); + return Optional.of(attributes); } - // Returns list of attributeDTOs. Can be empty. - return Optional.of(new ArrayList<>()); + // If no phrases found, return an empty list + return Optional.of(Collections.emptyList()); + } + + //create a senario where false is returned + @Transactional + public boolean loadSequence(PhraseSequenceRequest sequence){ + for (PhraseSequenceDataRequest phrase : sequence.getPhrases()) { + updatePhraseSequences(sequence.getUserId(), phrase); + } + + return true; + } + @Transactional + public void updatePhraseSequences(long userId, PhraseSequenceDataRequest phrase) { + + // Iterate over the list of phrases to update their positio + phraseSequenceRepository.storeOrUpdatePhrases( + userId, + phrase.phraseId, + phrase.sequence + ); + } } diff --git a/src/main/resources/db/migration/changelog-202404091617.xml b/src/main/resources/db/migration/changelog-202404091617.xml new file mode 100644 index 00000000..d3370587 --- /dev/null +++ b/src/main/resources/db/migration/changelog-202404091617.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/migration/changelog-202405071617.xml b/src/main/resources/db/migration/changelog-202405071617.xml new file mode 100644 index 00000000..18a57164 --- /dev/null +++ b/src/main/resources/db/migration/changelog-202405071617.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/migration/changelog-202407301617.xml b/src/main/resources/db/migration/changelog-202407301617.xml new file mode 100644 index 00000000..0640c322 --- /dev/null +++ b/src/main/resources/db/migration/changelog-202407301617.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO adverb (word) VALUES ('eagerly'); + INSERT INTO verb (word) VALUES ('runs'); + INSERT INTO noun (word) VALUES ('home'); + + INSERT INTO verb (word) VALUES ('walks'); + INSERT INTO noun (word) VALUES ('friend'); + + INSERT INTO preposition (word) VALUES ('with'); + + + + + + INSERT INTO phrase (id, adverb_id, verb_id, noun_id) VALUES (1, 101, 101, 101); + INSERT INTO phrase (id, verb_id, noun_id) VALUES (2, 102, 102); + INSERT INTO phrase (id, verb_id, preposition_id, noun_id) VALUES (3, 103, 101, 103); + + + + + + INSERT INTO user_phrase (user_id, phrase_id) VALUES (1, 1); + INSERT INTO user_phrase (user_id, phrase_id) VALUES (1, 2); + INSERT INTO user_phrase (user_id, phrase_id) VALUES (2, 2); + INSERT INTO user_phrase (user_id, phrase_id) VALUES (2, 3); + + + + + diff --git a/src/main/resources/db/migration/changelog-master.xml b/src/main/resources/db/migration/changelog-master.xml index f27bcfcc..2252d971 100644 --- a/src/main/resources/db/migration/changelog-master.xml +++ b/src/main/resources/db/migration/changelog-master.xml @@ -47,6 +47,7 @@ - + + diff --git a/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java b/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java index ff88cacd..d6c2e244 100644 --- a/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java +++ b/src/test/java/com/savvato/tribeapp/controllers/AttributesAPITest.java @@ -7,13 +7,13 @@ import com.savvato.tribeapp.constants.PhraseTestConstants; import com.savvato.tribeapp.constants.UserTestConstants; import com.savvato.tribeapp.controllers.dto.AttributesRequest; -import com.savvato.tribeapp.dto.AttributeDTO; -import com.savvato.tribeapp.dto.PhraseDTO; -import com.savvato.tribeapp.dto.ToBeReviewedDTO; -import com.savvato.tribeapp.dto.GenericResponseDTO; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceDataRequest; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceRequest; +import com.savvato.tribeapp.dto.*; import com.savvato.tribeapp.entities.NotificationType; import com.savvato.tribeapp.entities.User; import com.savvato.tribeapp.services.*; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -412,4 +412,46 @@ public void getUserPhrasesToBeReviewedHappyPathNoPhrases() throws Exception { gson.fromJson(result.getResponse().getContentAsString(), toBeReviewedDTOListType); assertThat(actualAttributes).isEmpty(); } + + @Test + public void testUPhraseSequences_Success() throws Exception { + PhraseSequenceRequest phraseSequenceRequest = new PhraseSequenceRequest(1L, Arrays.asList( + new PhraseSequenceDataRequest(1L, 1), + new PhraseSequenceDataRequest(2L, 2) + )); + when(attributesService.loadSequence(any(PhraseSequenceRequest.class))).thenReturn(true); + when(GenericResponseService.createDTO(anyBoolean())).thenReturn(GenericResponseDTO.builder().responseMessage("true").build()); + Mockito.when(userPrincipalService.getUserPrincipalByEmail(Mockito.anyString())).thenReturn(new UserPrincipal(user)); + String auth = AuthServiceImpl.generateAccessToken(user); + + MvcResult result = mockMvc.perform(post("/api/attributes/update") + .contentType(MediaType.APPLICATION_JSON) + .content(gson.toJson(phraseSequenceRequest)) + .header("Authorization", "Bearer " + auth)) + .andExpect(status().isOk()) + .andExpect(jsonPath("responseMessage").value("true")) + .andReturn(); + + Mockito.verify(attributesService).loadSequence(any(PhraseSequenceRequest.class)); + } + + @Test + public void testUPhraseSequences_Failure() throws Exception { + PhraseSequenceRequest phraseSequenceRequest = new PhraseSequenceRequest(1L, Arrays.asList( + new PhraseSequenceDataRequest(1L, 1), + new PhraseSequenceDataRequest(2L, 2) + )); + when(attributesService.loadSequence(any(PhraseSequenceRequest.class))).thenReturn(false); + Mockito.when(userPrincipalService.getUserPrincipalByEmail(Mockito.anyString())).thenReturn(new UserPrincipal(user)); + String auth = AuthServiceImpl.generateAccessToken(user); + + mockMvc.perform(post("/api/attributes/update") + .contentType(MediaType.APPLICATION_JSON) + .content(gson.toJson(phraseSequenceRequest)) + .header("Authorization", "Bearer " + auth)) + .andExpect(status().isInternalServerError()) + .andExpect(content().string(Matchers.isEmptyString())); + + Mockito.verify(attributesService).loadSequence(any(PhraseSequenceRequest.class)); + } } diff --git a/src/test/java/com/savvato/tribeapp/services/AttributesServiceImplTest.java b/src/test/java/com/savvato/tribeapp/services/AttributesServiceImplTest.java index 3eba0e27..63fe06ba 100644 --- a/src/test/java/com/savvato/tribeapp/services/AttributesServiceImplTest.java +++ b/src/test/java/com/savvato/tribeapp/services/AttributesServiceImplTest.java @@ -1,9 +1,13 @@ package com.savvato.tribeapp.services; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceDataRequest; +import com.savvato.tribeapp.controllers.dto.PhraseSequenceRequest; import com.savvato.tribeapp.constants.PhraseTestConstants; import com.savvato.tribeapp.constants.UserTestConstants; import com.savvato.tribeapp.dto.AttributeDTO; import com.savvato.tribeapp.dto.PhraseDTO; +import com.savvato.tribeapp.entities.PhraseSequence; +import com.savvato.tribeapp.repositories.PhraseSequenceRepository; import com.savvato.tribeapp.repositories.UserPhraseRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -14,13 +18,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.*; @@ -41,8 +42,26 @@ public AttributesService attributesService() { PhraseService phraseService; @MockBean UserPhraseRepository userPhraseRepository; + @MockBean + PhraseSequenceRepository phraseSequenceRepository; + @Test + public void testGetAttributesByUserId_EmptyPhrases() { + Long userId = 1L; + + // Mock an empty list of phrase sequences + when(phraseSequenceRepository.findByUserIdOrderByPosition(userId)).thenReturn(Collections.emptyList()); + + // Mock an empty Optional from phraseService + when(phraseService.getPhraseInformationByUserId(userId)).thenReturn(Optional.empty()); + + Optional> result = attributesService.getAttributesByUserId(userId); + + assertTrue(result.isPresent()); + assertTrue(result.get().isEmpty()); + } + public void getAttributesByUserIdWhenAttributesExist() { Long userId = USER1_ID; Map phraseInformation = @@ -77,17 +96,109 @@ public void getAttributesByUserIdWhenAttributesExist() { } @Test - public void getAttributesByUserIdWhenAttributesDontExist() { - Long userId = USER1_ID; - Optional> expected = Optional.of(new ArrayList<>()); - ArgumentCaptor userIdArgumentCaptor = ArgumentCaptor.forClass(Long.class); - when(phraseService.getPhraseInformationByUserId(anyLong())) - .thenReturn(Optional.empty()); + public void testGetAttributesByUserId_WithPhrases() { + Long userId = 1L; - Optional> actual = attributesService.getAttributesByUserId(userId); + // Create phrase sequences for this user + List phraseSequences = Arrays.asList( + new PhraseSequence(userId, 1L, 1), + new PhraseSequence(userId, 2L, 2) + ); - verify(phraseService, times(1)).getPhraseInformationByUserId(userIdArgumentCaptor.capture()); - assertEquals(userIdArgumentCaptor.getValue(), userId); - assertThat(actual).isEqualTo(expected); + // Create a map of PhraseDTO and their counts + Map phraseInfo = new HashMap<>(); + PhraseDTO phrase1 = PhraseDTO.builder().id(1L).adverb("enthusiastically").verb("volunteers").preposition("at").noun("UNICEF").build(); + PhraseDTO phrase2 = PhraseDTO.builder().id(2L).adverb("happily").verb("sings").preposition("at").noun("party").build(); + + phraseInfo.put(phrase1, 3); + phraseInfo.put(phrase2, 5); + + // Mock the responses from the repository and the service + when(phraseSequenceRepository.findByUserIdOrderByPosition(userId)).thenReturn(phraseSequences); + when(phraseService.getPhraseInformationByUserId(userId)).thenReturn(Optional.of(phraseInfo)); + + // Call the method and verify the results + Optional> result = attributesService.getAttributesByUserId(userId); + + assertTrue(result.isPresent()); + List attributes = result.get(); + + assertEquals(2, attributes.size()); + + // Validate the first attribute + AttributeDTO attr1 = attributes.get(0); + assertEquals(1L, attr1.phrase.id); + assertEquals("enthusiastically", attr1.phrase.adverb); + assertEquals("volunteers", attr1.phrase.verb); + assertEquals("at", attr1.phrase.preposition); + assertEquals("UNICEF", attr1.phrase.noun); + assertEquals(3, attr1.userCount); + assertEquals(1, attr1.sequence); + + // Validate the second attribute + AttributeDTO attr2 = attributes.get(1); + assertEquals(2L, attr2.phrase.id); + assertEquals("happily", attr2.phrase.adverb); + assertEquals("sings", attr2.phrase.verb); + assertEquals("at", attr2.phrase.preposition); + assertEquals("party", attr2.phrase.noun); + assertEquals(5, attr2.userCount); + assertEquals(2, attr2.sequence); } + + @Test + public void testLoadSequence() { + // Given + Long userId = 1L; + PhraseSequenceRequest sequence = new PhraseSequenceRequest(userId, Arrays.asList( + new PhraseSequenceDataRequest(1L, 1), + new PhraseSequenceDataRequest(2L, 2) + )); + + // When + boolean result = attributesService.loadSequence(sequence); + + // Then + verify(phraseSequenceRepository, times(1)).storeOrUpdatePhrases(userId, 1L, 1); + verify(phraseSequenceRepository, times(1)).storeOrUpdatePhrases(userId, 2L, 2); + assertTrue(result); + } + + @Test + public void testUpdatePhraseSequences() { + // Given + Long userId = 1L; + PhraseSequenceDataRequest phrase = new PhraseSequenceDataRequest(1L, 1); + + // When + attributesService.updatePhraseSequences(userId, phrase); + + // Then + verify(phraseSequenceRepository, times(1)).storeOrUpdatePhrases(userId, 1L, 1); + } + + @Test + public void testLoadSequence_WithEmptyPhrases() { + // Given + Long userId = 1L; + PhraseSequenceRequest sequence = new PhraseSequenceRequest(userId, Collections.emptyList()); + + // When + boolean result = attributesService.loadSequence(sequence); + + // Then + verify(phraseSequenceRepository, never()).storeOrUpdatePhrases(anyLong(), anyLong(), anyInt()); + assertTrue(result); + } + + @Test + public void testLoadSequence_WithNullPhrases() { + // Given + Long userId = 1L; + PhraseSequenceRequest sequence = new PhraseSequenceRequest(userId, null); + + // When and Then + assertThrows(NullPointerException.class, () -> attributesService.loadSequence(sequence)); + } + }