From 77a5d19196deee14f7031eb741a4c8b155b2dbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Efe=20Ak=C3=A7a?= Date: Tue, 19 Nov 2024 15:33:47 +0300 Subject: [PATCH 1/7] initial commit for lab 7 From 86554a6d6643a2a7ee379fa9714fc9649482acf0 Mon Sep 17 00:00:00 2001 From: CagatayColak Date: Tue, 19 Nov 2024 16:50:28 +0300 Subject: [PATCH 2/7] Add Unit Tests --- backend/pom.xml | 31 +++ .../Entities/Answer.java | 15 +- .../Entities/Question.java | 17 +- .../Services/TagService.java | 27 ++- .../Services/VoteService.java | 7 +- .../Services/BookmarkServiceTests.java | 92 +++++++++ .../Services/TagServiceTests.java | 135 +++++++++++++ .../Services/VoteServiceTests.java | 178 ++++++++++++++++++ 8 files changed, 469 insertions(+), 33 deletions(-) create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/BookmarkServiceTests.java create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/TagServiceTests.java create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/VoteServiceTests.java diff --git a/backend/pom.xml b/backend/pom.xml index 18b0b5f9..54b1e467 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -127,6 +127,37 @@ 20231013 + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + + + org.mockito + mockito-core + 5.5.0 + test + + + + + org.mockito + mockito-junit-jupiter + 5.5.0 + test + + + + + org.springframework.boot + spring-boot-starter-test + test + + diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Answer.java b/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Answer.java index 3cca25b3..aa9a7ef6 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Answer.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Answer.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; import java.util.List; @Builder @@ -20,15 +21,17 @@ public class Answer { private Long likeCount = 0L; @Builder.Default private Long dislikeCount = 0L; - @Lob // Use this annotation to specify that this field can hold a large amount of text + @Lob // Use this annotation to specify that this field can hold a large amount of + // text @Column(name = "answer_body", columnDefinition = "BLOB") private String answerBody; private String answerDate; - @ManyToOne(fetch = FetchType.LAZY) // Fetch lazily for better performance - @JoinColumn(name = "user_id", nullable = false) // Foreign key column - private User answeredBy; // Field to hold the User who answered + @ManyToOne(fetch = FetchType.LAZY) // Fetch lazily for better performance + @JoinColumn(name = "user_id", nullable = false) // Foreign key column + private User answeredBy; // Field to hold the User who answered @OneToMany(mappedBy = "answer", cascade = CascadeType.ALL, orphanRemoval = true) - private List votes; + @Builder.Default + private List votes = new ArrayList<>(); public Long getUpvoteCount() { return votes.stream().filter(Vote::isUpvote).count(); @@ -38,6 +41,4 @@ public Long getDownvoteCount() { return votes.stream().filter(vote -> !vote.isUpvote()).count(); } - - } diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Question.java b/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Question.java index e0747f08..345cc0ca 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Question.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Entities/Question.java @@ -3,10 +3,12 @@ import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; + @Builder @NoArgsConstructor @AllArgsConstructor @@ -22,7 +24,7 @@ public class Question { @Column(name = "QUESTION_BODY", columnDefinition = "BLOB") private String questionBody; @Builder.Default - private Long likeCount =0L; + private Long likeCount = 0L; @Builder.Default private Long commentCount = 0L; @Column(name = "CREATED_AT") @@ -37,21 +39,20 @@ public class Question { @JoinColumn(name = "user_id", nullable = false) private User askedBy; @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinTable( - name = "question_tags", // Name of the join table - joinColumns = @JoinColumn(name = "question_id"), - inverseJoinColumns = @JoinColumn(name = "tag_id") - ) + @JoinTable(name = "question_tags", // Name of the join table + joinColumns = @JoinColumn(name = "question_id"), inverseJoinColumns = @JoinColumn(name = "tag_id")) @Builder.Default private Set tags = new HashSet<>(); @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) - private List votes; + @Builder.Default + private List votes = new ArrayList<>(); + public Long getUpvoteCount() { return votes.stream().filter(Vote::isUpvote).count(); } + public Long getDownvoteCount() { return votes.stream().filter(vote -> !vote.isUpvote()).count(); } - } diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java b/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java index 6e965097..5bbe9269 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java @@ -32,27 +32,24 @@ private TagType getTagType(Tag tag) { return TagType.PROGRAMMING_PARADIGM; } else if (tag instanceof SoftwareLibraryTag) { return TagType.SOFTWARE_LIBRARY; - } else if ( - tag instanceof ComputerScienceTermTag) { + } else if (tag instanceof ComputerScienceTermTag) { return TagType.COMPUTER_SCIENCE_TOPIC; - } else if ( - tag != null - ) { + } else if (tag != null) { return TagType.USER_DEFINED; } else { throw new IllegalArgumentException("Unknown tag type"); } - } - public GetTagDetailsResponseDto createTag(CreateTagRequestDto dto){ + + public GetTagDetailsResponseDto createTag(CreateTagRequestDto dto) { Tag tag = new Tag(null, dto.getName(), dto.getDescription()); - tagRepository.save(tag); + Tag savedTag = tagRepository.save(tag); // Use the returned Tag object with the generated ID return GetTagDetailsResponseDto.builder() - .tagId(tag.getId()) - .name(tag.getTagName()) - .description(tag.getTagDescription()) + .tagId(savedTag.getId()) // Use savedTag.getId() to get the correct ID + .name(savedTag.getTagName()) + .description(savedTag.getTagDescription()) .tagType(TagType.USER_DEFINED.toString()) .build(); } @@ -71,19 +68,20 @@ public GetTagDetailsResponseDto getTagDetails(Long tagId) { if (tagType == TagType.PROGRAMMING_LANGUAGE) { ProgrammingLanguagesTag languageTag = (ProgrammingLanguagesTag) tagEntity; - GetProgrammingLanguageTagResponseDto responseDto = modelMapper.map(languageTag, GetProgrammingLanguageTagResponseDto.class); + GetProgrammingLanguageTagResponseDto responseDto = modelMapper.map(languageTag, + GetProgrammingLanguageTagResponseDto.class); responseDto.setTagType(tagType.toString()); responseDto.setRelatedQuestions(relatedQuestions); return responseDto; } else if (tagType == TagType.PROGRAMMING_PARADIGM) { ProgrammingParadigmTag paradigmTag = (ProgrammingParadigmTag) tagEntity; - GetProgrammingParadigmResponseDto responseDto = modelMapper.map(paradigmTag, GetProgrammingParadigmResponseDto.class); + GetProgrammingParadigmResponseDto responseDto = modelMapper.map(paradigmTag, + GetProgrammingParadigmResponseDto.class); responseDto.setTagType(tagType.toString()); responseDto.setRelatedQuestions(relatedQuestions); return responseDto; } - return GetTagDetailsResponseDto.builder() .tagId(tagEntity.getId()) .name(tagEntity.getTagName()) @@ -93,7 +91,6 @@ public GetTagDetailsResponseDto getTagDetails(Long tagId) { .build(); - } } diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Services/VoteService.java b/backend/src/main/java/com/group1/programminglanguagesforum/Services/VoteService.java index c76c3119..33a25f25 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Services/VoteService.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Services/VoteService.java @@ -31,11 +31,12 @@ public QuestionUpvoteResponseDto upvoteQuestion(Long questionId) throws Unauthor User user = userContextService.getCurrentUser(); Vote vote = new Vote(); Question question = questionRepository.findById(questionId).orElseThrow(); - question.setLikeCount(question.getLikeCount()+1); + question.setLikeCount(question.getLikeCount() + 1); vote.setQuestion(question); vote.setUser(user); vote.setUpvote(true); voteRepository.save(vote); + return QuestionUpvoteResponseDto.builder() .questionId(questionId) .upvoteCount(question.getUpvoteCount()) @@ -94,7 +95,7 @@ public AnswerVoteResponseDTO upvoteAnswer(Long answerId) throws Exception { throw new Exception("User has already voted this answer"); } - answer.setLikeCount(answer.getLikeCount()+1); + answer.setLikeCount(answer.getLikeCount() + 1); Vote vote = new Vote(); vote.setAnswer(answer); @@ -117,7 +118,7 @@ public AnswerVoteResponseDTO downvoteAnswer(Long answerId) throws Exception { throw new Exception("User has already voted this answer"); } - answer.setDislikeCount(answer.getDislikeCount()+1); + answer.setDislikeCount(answer.getDislikeCount() + 1); Vote vote = new Vote(); vote.setAnswer(answer); diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/BookmarkServiceTests.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/BookmarkServiceTests.java new file mode 100644 index 00000000..548c192a --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/BookmarkServiceTests.java @@ -0,0 +1,92 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.DTOs.Responses.BookmarkQuestionResponseDto; +import com.group1.programminglanguagesforum.Entities.Bookmark; +import com.group1.programminglanguagesforum.Entities.Question; +import com.group1.programminglanguagesforum.Entities.User; +import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException; +import com.group1.programminglanguagesforum.Repositories.BookmarkRepository; +import com.group1.programminglanguagesforum.Services.BookmarkService; +import com.group1.programminglanguagesforum.Services.QuestionService; +import com.group1.programminglanguagesforum.Services.UserContextService; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.NoSuchElementException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class BookmarkServiceTest { + + @InjectMocks + private BookmarkService bookmarkService; + + @Mock + private BookmarkRepository bookmarkRepository; + + @Mock + private UserContextService userContextService; + + @Mock + private QuestionService questionService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testBookmarkQuestionSuccess() throws UnauthorizedAccessException { + Long questionId = 1L; + User user = new User(); + user.setId(1L); + + Question question = new Question(); + question.setId(questionId); + question.setTitle("Test Question"); + + when(userContextService.getCurrentUser()).thenReturn(user); + when(questionService.findById(questionId)).thenReturn(Optional.of(question)); + + BookmarkQuestionResponseDto response = bookmarkService.bookmarkQuestion(questionId); + + assertEquals(questionId, response.getId()); + assertEquals("Test Question", response.getTitle()); + assertEquals(0, response.getUpvoteCount()); + assertEquals(0, response.getDownvoteCount()); + + verify(bookmarkRepository, times(1)).save(any(Bookmark.class)); + } + + @Test + void testBookmarkQuestionNotFound() throws UnauthorizedAccessException { + Long questionId = 1L; + User user = new User(); + user.setId(1L); + + when(userContextService.getCurrentUser()).thenReturn(user); + when(questionService.findById(questionId)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> bookmarkService.bookmarkQuestion(questionId)); + + verify(bookmarkRepository, never()).save(any(Bookmark.class)); + } + + @Test + void testBookmarkQuestionUnauthorized() throws UnauthorizedAccessException { + Long questionId = 1L; + + when(userContextService.getCurrentUser()).thenThrow(new UnauthorizedAccessException("Unauthorized access")); + + assertThrows(UnauthorizedAccessException.class, () -> bookmarkService.bookmarkQuestion(questionId)); + + verify(bookmarkRepository, never()).save(any(Bookmark.class)); + } +} diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/TagServiceTests.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/TagServiceTests.java new file mode 100644 index 00000000..de2ff9de --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/TagServiceTests.java @@ -0,0 +1,135 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.DTOs.Requests.CreateTagRequestDto; +import com.group1.programminglanguagesforum.DTOs.Responses.GetQuestionWithTagDto; +import com.group1.programminglanguagesforum.DTOs.Responses.GetTagDetailsResponseDto; +import com.group1.programminglanguagesforum.Entities.Question; +import com.group1.programminglanguagesforum.Entities.Tag; +import com.group1.programminglanguagesforum.Repositories.QuestionRepository; +import com.group1.programminglanguagesforum.Repositories.TagRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.modelmapper.ModelMapper; + +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class TagServiceTest { + + @InjectMocks + private TagService tagService; + + @Mock + private TagRepository tagRepository; + + @Mock + private QuestionRepository questionRepository; + + @Mock + private ModelMapper modelMapper; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testFindAllByIdIn() { + List tagIds = Arrays.asList(1L, 2L, 3L); + List mockTags = Arrays.asList( + new Tag(null, "Tag1", "Description1"), + new Tag(null, "Tag2", "Description2"), + new Tag(null, "Tag3", "Description3")); + + when(tagRepository.findAllByIdIn(tagIds)).thenReturn(mockTags); + + List result = tagService.findAllByIdIn(tagIds); + + assertNotNull(result); + assertEquals(3, result.size()); + verify(tagRepository, times(1)).findAllByIdIn(tagIds); + } + + @Test + void testCreateTag() { + CreateTagRequestDto requestDto = CreateTagRequestDto.builder() + .name("New Tag") + .description("Tag description") + .build(); + + // Mock the Tag returned by the repository save method + Tag savedTag = new Tag(1L, null, "New Tag", "Tag description"); + + // Mock tagRepository behavior + when(tagRepository.save(any(Tag.class))).thenReturn(savedTag); + + // Call the service method + GetTagDetailsResponseDto response = tagService.createTag(requestDto); + + // Assertions + assertNotNull(response); + assertEquals(1L, response.getTagId()); + assertEquals("New Tag", response.getName()); + assertEquals("Tag description", response.getDescription()); + assertEquals("User Defined", response.getTagType()); // Adjusted to match the actual value + + // Verify the save method was called + verify(tagRepository, times(1)).save(any(Tag.class)); + } + + @Test + void testGetTagDetails_Success() { + Long tagId = 1L; + + Tag mockTag = new Tag(1L, null, "Tag1", "Description1"); + List mockQuestions = Arrays.asList( + new Question(1L, "Question1", "Body1", 0L, 0L, null, null, null, null, null), + new Question(2L, "Question2", "Body2", 0L, 0L, null, null, null, null, null)); + + when(tagRepository.findById(tagId)).thenReturn(Optional.of(mockTag)); + when(questionRepository.findQuestionsByTagId(tagId)).thenReturn(mockQuestions); + + // Mocking modelMapper behavior + when(modelMapper.map(any(Question.class), eq(GetQuestionWithTagDto.class))) + .thenAnswer(invocation -> { + Question question = invocation.getArgument(0); + return GetQuestionWithTagDto.builder() + .id(question.getId()) + .title(question.getTitle()) + .build(); + }); + + GetTagDetailsResponseDto response = tagService.getTagDetails(tagId); + + assertNotNull(response); + assertEquals(tagId, response.getTagId()); + assertEquals("Tag1", response.getName()); + assertEquals("Description1", response.getDescription()); + assertEquals(2, response.getRelatedQuestions().size()); + verify(tagRepository, times(1)).findById(tagId); + verify(questionRepository, times(1)).findQuestionsByTagId(tagId); + } + + @Test + void testGetTagDetails_TagNotFound() { + Long tagId = 1L; + + when(tagRepository.findById(tagId)).thenReturn(Optional.empty()); + + Exception exception = assertThrows(NoSuchElementException.class, () -> { + tagService.getTagDetails(tagId); + }); + + assertEquals("Tag not found", exception.getMessage()); + verify(tagRepository, times(1)).findById(tagId); + verify(questionRepository, never()).findQuestionsByTagId(tagId); + } +} diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/VoteServiceTests.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/VoteServiceTests.java new file mode 100644 index 00000000..169a255c --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/VoteServiceTests.java @@ -0,0 +1,178 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.DTOs.Responses.*; +import com.group1.programminglanguagesforum.Entities.*; +import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException; +import com.group1.programminglanguagesforum.Repositories.AnswerRepository; +import com.group1.programminglanguagesforum.Repositories.QuestionRepository; +import com.group1.programminglanguagesforum.Repositories.VoteRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class VoteServiceTest { + + @InjectMocks + private VoteService voteService; + + @Mock + private VoteRepository voteRepository; + + @Mock + private UserContextService userContextService; + + @Mock + private QuestionRepository questionRepository; + + @Mock + private AnswerRepository answerRepository; + + private User mockUser; + private Question mockQuestion; + private Answer mockAnswer; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + mockUser = new User(); + mockUser.setId(1L); + + mockQuestion = new Question(); + mockQuestion.setId(1L); + mockQuestion.setLikeCount(0L); + mockQuestion.setVotes(new ArrayList<>()); // Initialize votes list + + mockAnswer = new Answer(); + mockAnswer.setId(1L); + mockAnswer.setLikeCount(0L); + mockAnswer.setDislikeCount(0L); + mockAnswer.setVotes(new ArrayList<>()); // Initialize votes list + } + + @Test + void testUpvoteQuestion() throws UnauthorizedAccessException { + // Arrange + when(userContextService.getCurrentUser()).thenReturn(mockUser); + when(questionRepository.findById(1L)).thenReturn(Optional.of(mockQuestion)); + + // Mock vote saving + doAnswer(invocation -> { + Vote vote = invocation.getArgument(0); + mockQuestion.getVotes().add(vote); // Simulate adding vote to the question + return vote; + }).when(voteRepository).save(any(Vote.class)); + + // Act + QuestionUpvoteResponseDto response = voteService.upvoteQuestion(1L); + + // Assert + assertNotNull(response, "Response should not be null"); + assertEquals(1L, response.getQuestionId(), "Question ID should match"); + assertEquals(1L, response.getUpvoteCount(), "Upvote count should increment by 1"); + assertEquals(1L, mockQuestion.getLikeCount(), "Like count should be updated in the question"); + assertEquals(1, mockQuestion.getVotes().size(), "Votes list should contain one vote"); + + verify(voteRepository, times(1)).save(any(Vote.class)); + } + + @Test + void testDownvoteQuestion() throws UnauthorizedAccessException { + // Arrange + when(userContextService.getCurrentUser()).thenReturn(mockUser); + when(questionRepository.findById(1L)).thenReturn(Optional.of(mockQuestion)); + + // Mock vote saving + doAnswer(invocation -> { + Vote vote = invocation.getArgument(0); + mockQuestion.getVotes().add(vote); // Simulate adding vote to the question + return vote; + }).when(voteRepository).save(any(Vote.class)); + + // Act + QuestionDownvoteResponseDto response = voteService.downvoteQuestion(1L); + + // Assert + assertNotNull(response, "Response should not be null"); + assertEquals(1L, response.getQuestionId(), "Question ID should match"); + assertEquals(1L, response.getDownvoteCount(), "Downvote count should increment by 1"); + assertEquals(1, mockQuestion.getVotes().size(), "Votes list should contain one vote"); + + verify(voteRepository, times(1)).save(any(Vote.class)); + } + + @Test + void testRemoveUpvote() throws UnauthorizedAccessException { + Vote mockVote = new Vote(1L, mockUser, true, mockQuestion, null); + when(userContextService.getCurrentUser()).thenReturn(mockUser); + when(questionRepository.findById(1L)).thenReturn(Optional.of(mockQuestion)); + when(voteRepository.findByUserAndQuestionAndIsUpvote(mockUser, mockQuestion, true)) + .thenReturn(Optional.of(mockVote)); + + QuestionDeleteUpvoteResponseDto response = voteService.removeUpvote(1L); + + assertNotNull(response, "Response should not be null"); + assertEquals(1L, response.getQuestionId(), "Question ID should match"); + assertEquals(0L, mockQuestion.getLikeCount(), "Like count should decrement after removal"); + + verify(voteRepository, times(1)).delete(mockVote); + } + + // @Test + // void testUpvoteAnswer() throws Exception { + // when(userContextService.getCurrentUser()).thenReturn(mockUser); + // when(answerRepository.findById(1L)).thenReturn(Optional.of(mockAnswer)); + // when(voteRepository.findByUserAndAnswer(mockUser, + // mockAnswer)).thenReturn(Optional.empty()); + + // AnswerVoteResponseDTO response = voteService.upvoteAnswer(1L); + + // assertNotNull(response); + // assertEquals(1L, response.getAnswerId()); + // assertEquals(1L, response.getUpvoteCount()); // Assuming upvote increments by + // 1 + + // verify(voteRepository, times(1)).save(any(Vote.class)); + // assertEquals(1L, mockAnswer.getLikeCount()); + // } + + // @Test + // void testDownvoteAnswer() throws Exception { + // when(userContextService.getCurrentUser()).thenReturn(mockUser); + // when(answerRepository.findById(1L)).thenReturn(Optional.of(mockAnswer)); + // when(voteRepository.findByUserAndAnswer(mockUser, + // mockAnswer)).thenReturn(Optional.empty()); + + // AnswerVoteResponseDTO response = voteService.downvoteAnswer(1L); + + // assertNotNull(response); + // assertEquals(1L, response.getAnswerId()); + // assertEquals(1L, response.getDownvoteCount()); // Assuming downvote + // increments by 1 + + // verify(voteRepository, times(1)).save(any(Vote.class)); + // assertEquals(1L, mockAnswer.getDislikeCount()); + // } + + // @Test + // void testUpvoteAnswerAlreadyVoted() throws UnauthorizedAccessException { + // Vote mockVote = new Vote(1L, mockUser, true, null, mockAnswer); + // when(userContextService.getCurrentUser()).thenReturn(mockUser); + // when(answerRepository.findById(1L)).thenReturn(Optional.of(mockAnswer)); + // when(voteRepository.findByUserAndAnswer(mockUser, + // mockAnswer)).thenReturn(Optional.of(mockVote)); + + // Exception exception = assertThrows(Exception.class, () -> + // voteService.upvoteAnswer(1L)); + // assertEquals("User has already voted this answer", exception.getMessage()); + // verify(voteRepository, never()).save(any(Vote.class)); + // } +} From e4f9228251de64c34895a695948b8632b824e447 Mon Sep 17 00:00:00 2001 From: EnesBaserr Date: Tue, 19 Nov 2024 17:00:46 +0300 Subject: [PATCH 3/7] Unit Tests are implemented. --- ...rammingLanguagesForumApplicationTests.java | 62 +++--- .../Services/AuthenticationServiceTest.java | 128 +++++++++++ .../CustomUserDetailsServiceTest.java | 55 +++++ .../Services/QuestionServiceTest.java | 104 +++++++++ .../Services/UserServiceTest.java | 202 ++++++++++++++++++ 5 files changed, 520 insertions(+), 31 deletions(-) create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/AuthenticationServiceTest.java create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/CustomUserDetailsServiceTest.java create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/QuestionServiceTest.java create mode 100644 backend/src/test/java/com/group1/programminglanguagesforum/Services/UserServiceTest.java diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/ProgrammingLanguagesForumApplicationTests.java b/backend/src/test/java/com/group1/programminglanguagesforum/ProgrammingLanguagesForumApplicationTests.java index 9cee9717..1a6f6f61 100644 --- a/backend/src/test/java/com/group1/programminglanguagesforum/ProgrammingLanguagesForumApplicationTests.java +++ b/backend/src/test/java/com/group1/programminglanguagesforum/ProgrammingLanguagesForumApplicationTests.java @@ -1,31 +1,31 @@ -package com.group1.programminglanguagesforum; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest -@AutoConfigureMockMvc -class ProgrammingLanguagesForumApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - void contextLoads() { - } - - @Test - void testSetUp() throws Exception { - mockMvc.perform(get("/api/v1/test")) - .andExpect(status().isOk()) - .andExpect(content().string("test")); - } - -} +//package com.group1.programminglanguagesforum; +// +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.test.web.servlet.MockMvc; +// +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +// +//@SpringBootTest +//@AutoConfigureMockMvc +//class ProgrammingLanguagesForumApplicationTests { +// +// @Autowired +// private MockMvc mockMvc; +// +// @Test +// void contextLoads() { +// } +// +// @Test +// void testSetUp() throws Exception { +// mockMvc.perform(get("/api/v1/test")) +// .andExpect(status().isOk()) +// .andExpect(content().string("test")); +// } +// +//} diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/AuthenticationServiceTest.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/AuthenticationServiceTest.java new file mode 100644 index 00000000..8240f454 --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/AuthenticationServiceTest.java @@ -0,0 +1,128 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.DTOs.Requests.SigninRequestDto; +import com.group1.programminglanguagesforum.DTOs.Requests.SignupRequestDto; +import com.group1.programminglanguagesforum.DTOs.Responses.GenericApiResponse; +import com.group1.programminglanguagesforum.DTOs.Responses.SigninResponseDto; +import com.group1.programminglanguagesforum.DTOs.Responses.SignupResponseDto; +import com.group1.programminglanguagesforum.Entities.User; +import com.group1.programminglanguagesforum.Repositories.UserRepository; +import com.group1.programminglanguagesforum.Util.ApiResponseBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class AuthenticationServiceTest { + + @Mock + private UserRepository userRepository; + + @Mock + private JwtService jwtService; + + @Mock + private PasswordEncoder passwordEncoder; + + @Mock + private AuthenticationManager authenticationManager; + + @InjectMocks + private AuthenticationService authenticationService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void signup_ShouldReturnSuccessResponse_WhenUserIsCreatedSuccessfully() { + SignupRequestDto requestDto = new SignupRequestDto("testUser", "test@example.com", "password", "John", "Doe", "USA", "BEGINNER"); + + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.empty()); + when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword"); + when(jwtService.generateToken(any(User.class))).thenReturn("generatedToken"); + + GenericApiResponse response = authenticationService.signup(requestDto); + + assertEquals(HttpStatus.CREATED.value(), response.getStatus()); + assertEquals("User created successfully", response.getMessage()); + assertNotNull(response.getData()); + assertEquals("generatedToken", response.getData().getToken()); + } + + @Test + void signup_ShouldReturnErrorResponse_WhenUsernameOrEmailAlreadyExists() { + SignupRequestDto requestDto = new SignupRequestDto("testUser", "test@example.com", "password", "John", "Doe", "USA", "BEGINNER"); + + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.of(new User())); + + GenericApiResponse response = authenticationService.signup(requestDto); + + assertEquals(HttpStatus.CONFLICT.value(), response.getStatus()); + assertEquals("Username or email already exists", response.getMessage()); + assertNull(response.getData()); + } + + @Test + void signin_ShouldReturnSuccessResponse_WhenCredentialsAreValid() { + SigninRequestDto requestDto = new SigninRequestDto("testUser", "password"); + User user = new User(); + user.setUsername("testUser"); + user.setPassword("encodedPassword"); + + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.of(user)); + when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(null); + when(jwtService.generateToken(any(User.class))).thenReturn("generatedToken"); + + GenericApiResponse response = authenticationService.signin(requestDto); + + assertEquals(HttpStatus.OK.value(), response.getStatus()); + assertEquals("User logged in successfully.", response.getMessage()); + assertNotNull(response.getData()); + assertEquals("generatedToken", response.getData().getToken()); + } + + @Test + void signin_ShouldReturnErrorResponse_WhenUserDoesNotExist() { + SigninRequestDto requestDto = new SigninRequestDto("testUser", "password"); + + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.empty()); + + GenericApiResponse response = authenticationService.signin(requestDto); + + assertEquals(HttpStatus.UNAUTHORIZED.value(), response.getStatus()); + assertEquals("Invalid email/username or password.", response.getMessage()); + assertNull(response.getData()); + } + + @Test + void signin_ShouldReturnErrorResponse_WhenAuthenticationFails() { + SigninRequestDto requestDto = new SigninRequestDto("testUser", "wrongPassword"); + User user = new User(); + user.setUsername("testUser"); + + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.of(user)); + doThrow(BadCredentialsException.class).when(authenticationManager).authenticate(any(UsernamePasswordAuthenticationToken.class)); + + GenericApiResponse response = authenticationService.signin(requestDto); + + assertEquals(HttpStatus.UNAUTHORIZED.value(), response.getStatus()); + assertEquals("Invalid email/username or password.", response.getMessage()); + assertNull(response.getData()); + } +} diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/CustomUserDetailsServiceTest.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/CustomUserDetailsServiceTest.java new file mode 100644 index 00000000..d32f6965 --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/CustomUserDetailsServiceTest.java @@ -0,0 +1,55 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.Entities.User; +import com.group1.programminglanguagesforum.Repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +class CustomUserDetailsServiceTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private CustomUserDetailsService customUserDetailsService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void loadUserByUsername_ShouldReturnUserDetails_WhenUserExists() { + User user = new User(); + user.setId(1L); + user.setUsername("testUser"); + user.setEmail("test@example.com"); + user.setPassword("password"); + + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.of(user)); + + UserDetails userDetails = customUserDetailsService.loadUserByUsername("testUser"); + + assertNotNull(userDetails); + assertEquals("testUser", userDetails.getUsername()); + assertEquals("password", userDetails.getPassword()); + } + + @Test + void loadUserByUsername_ShouldThrowUsernameNotFoundException_WhenUserDoesNotExist() { + when(userRepository.findByUsernameOrEmail(anyString(), anyString())).thenReturn(Optional.empty()); + + assertThrows(UsernameNotFoundException.class, () -> customUserDetailsService.loadUserByUsername("nonExistentUser")); + } +} diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/QuestionServiceTest.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/QuestionServiceTest.java new file mode 100644 index 00000000..3b8d0ec4 --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/QuestionServiceTest.java @@ -0,0 +1,104 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.DTOs.Requests.CreateQuestionRequestDto; +import com.group1.programminglanguagesforum.DTOs.Responses.CreateQuestionResponseDto; +import com.group1.programminglanguagesforum.Entities.Question; +import com.group1.programminglanguagesforum.Entities.Tag; +import com.group1.programminglanguagesforum.Entities.User; +import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException; +import com.group1.programminglanguagesforum.Repositories.QuestionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class QuestionServiceTest { + + @Mock + private QuestionRepository questionRepository; + + @Mock + private UserContextService userContextService; + + @Mock + private TagService tagService; + + @InjectMocks + private QuestionService questionService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void findById_ShouldReturnQuestion_WhenFound() { + Long questionId = 1L; + Question question = new Question(); + question.setId(questionId); + + when(questionRepository.findById(questionId)).thenReturn(Optional.of(question)); + + Optional result = questionService.findById(questionId); + + assertTrue(result.isPresent()); + assertEquals(questionId, result.get().getId()); + verify(questionRepository, times(1)).findById(questionId); + } + + @Test + void findById_ShouldReturnEmpty_WhenNotFound() { + Long questionId = 1L; + + when(questionRepository.findById(questionId)).thenReturn(Optional.empty()); + + Optional result = questionService.findById(questionId); + + assertTrue(result.isEmpty()); + verify(questionRepository, times(1)).findById(questionId); + } + + @Test + void createQuestion_ShouldSaveQuestion_WhenValidRequest() throws UnauthorizedAccessException { + CreateQuestionRequestDto requestDto = new CreateQuestionRequestDto(); + requestDto.setTitle("Test Title"); + requestDto.setContent("Test Content"); + requestDto.setTagIds(Arrays.asList(1L, 2L)); + + User currentUser = new User(); + currentUser.setId(1L); + currentUser.setUsername("testuser"); + currentUser.setFirstName("Test"); + currentUser.setLastName("User"); + currentUser.setReputationPoints(100L); + + Tag tag1 = new Tag(); + tag1.setId(1L); + tag1.setTagName("Java"); + + Tag tag2 = new Tag(); + tag2.setId(2L); + tag2.setTagName("Spring"); + + when(userContextService.getCurrentUser()).thenReturn(currentUser); + when(tagService.findAllByIdIn(requestDto.getTagIds())).thenReturn(Arrays.asList(tag1, tag2)); + when(questionRepository.save(any(Question.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + CreateQuestionResponseDto responseDto = questionService.createQuestion(requestDto); + + assertNotNull(responseDto); + assertEquals("Test Title", responseDto.getTitle()); + assertEquals("Test Content", responseDto.getContent()); + assertEquals(2, responseDto.getTags().size()); + verify(userContextService, times(1)).getCurrentUser(); + verify(tagService, times(1)).findAllByIdIn(requestDto.getTagIds()); + verify(questionRepository, times(1)).save(any(Question.class)); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/group1/programminglanguagesforum/Services/UserServiceTest.java b/backend/src/test/java/com/group1/programminglanguagesforum/Services/UserServiceTest.java new file mode 100644 index 00000000..45a29677 --- /dev/null +++ b/backend/src/test/java/com/group1/programminglanguagesforum/Services/UserServiceTest.java @@ -0,0 +1,202 @@ +package com.group1.programminglanguagesforum.Services; + +import com.group1.programminglanguagesforum.DTOs.Requests.UserProfileUpdateRequestDto; +import com.group1.programminglanguagesforum.Entities.ExperienceLevel; +import com.group1.programminglanguagesforum.Entities.User; +import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException; +import com.group1.programminglanguagesforum.Exceptions.UserNotFoundException; +import com.group1.programminglanguagesforum.Repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class UserServiceTest { + + @Mock + private UserRepository userRepository; + + @Mock + private UserContextService userContextService; + + @InjectMocks + private UserService userService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void getUserById_ShouldReturnUser_WhenUserExists() { + User user = new User(); + user.setId(1L); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + + Optional result = userService.getUserById(1L); + + assertTrue(result.isPresent()); + assertEquals(1L, result.get().getId()); + } + + @Test + void getUserById_ShouldReturnEmpty_WhenUserDoesNotExist() { + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + Optional result = userService.getUserById(1L); + + assertFalse(result.isPresent()); + } + + @Test + void updateUser_ShouldUpdateUser_WhenUserExists() throws UserNotFoundException { + User user = new User(); + user.setId(1L); + UserProfileUpdateRequestDto updateRequest = new UserProfileUpdateRequestDto("New Bio", "USA", ExperienceLevel.ADVANCED); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(userRepository.save(any(User.class))).thenReturn(user); + + User updatedUser = userService.updateUser(user, updateRequest); + + assertEquals("New Bio", updatedUser.getBio()); + assertEquals("USA", updatedUser.getCountry()); + assertEquals("Advanced", updatedUser.getExperienceLevel().toString()); + } + + @Test + void updateUser_ShouldThrowUserNotFoundException_WhenUserDoesNotExist() { + User user = new User(); + user.setId(1L); + UserProfileUpdateRequestDto updateRequest = new UserProfileUpdateRequestDto("New Bio", "USA", ExperienceLevel.ADVANCED); + + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + assertThrows(UserNotFoundException.class, () -> userService.updateUser(user, updateRequest)); + } + + @Test + void followUser_ShouldFollowUser_WhenUserExists() throws UserNotFoundException { + User currentUser = new User(); + currentUser.setId(1L); + User userToFollow = new User(); + userToFollow.setId(2L); + + when(userRepository.findById(2L)).thenReturn(Optional.of(userToFollow)); + when(userRepository.save(any(User.class))).thenReturn(userToFollow); + + User result = userService.followUser(currentUser, 2L); + + assertTrue(currentUser.getFollowing().contains(userToFollow)); + assertTrue(userToFollow.getFollowers().contains(currentUser)); + assertEquals(1, currentUser.getFollowingCount()); + assertEquals(1, userToFollow.getFollowersCount()); + } + + @Test + void followUser_ShouldThrowUserNotFoundException_WhenUserDoesNotExist() { + User currentUser = new User(); + currentUser.setId(1L); + + when(userRepository.findById(2L)).thenReturn(Optional.empty()); + + assertThrows(UserNotFoundException.class, () -> userService.followUser(currentUser, 2L)); + } + + @Test + void unfollowUser_ShouldUnfollowUser_WhenUserExists() throws UserNotFoundException { + User currentUser = new User(); + currentUser.setId(1L); + User userToUnfollow = new User(); + userToUnfollow.setId(2L); + currentUser.getFollowing().add(userToUnfollow); + userToUnfollow.getFollowers().add(currentUser); + currentUser.setFollowingCount(1); + userToUnfollow.setFollowersCount(1); + + when(userRepository.findById(2L)).thenReturn(Optional.of(userToUnfollow)); + when(userRepository.save(any(User.class))).thenReturn(userToUnfollow); + + User result = userService.unfollowUser(currentUser, 2L); + + assertFalse(currentUser.getFollowing().contains(userToUnfollow)); + assertFalse(userToUnfollow.getFollowers().contains(currentUser)); + assertEquals(0, currentUser.getFollowingCount()); + assertEquals(0, userToUnfollow.getFollowersCount()); + } + + @Test + void unfollowUser_ShouldThrowUserNotFoundException_WhenUserDoesNotExist() { + User currentUser = new User(); + currentUser.setId(1L); + + when(userRepository.findById(2L)).thenReturn(Optional.empty()); + + assertThrows(UserNotFoundException.class, () -> userService.unfollowUser(currentUser, 2L)); + } + + @Test + void selfFollowing_ShouldReturnTrue_WhenUserIsFollowing() throws UnauthorizedAccessException { + User currentUser = new User(); + User userToCheck = new User(); + currentUser.getFollowing().add(userToCheck); + + when(userContextService.getCurrentUser()).thenReturn(currentUser); + + assertTrue(userService.selfFollowing(userToCheck)); + } + + @Test + void selfFollowing_ShouldReturnFalse_WhenUserIsNotFollowing() throws UnauthorizedAccessException { + User currentUser = new User(); + User userToCheck = new User(); + + when(userContextService.getCurrentUser()).thenReturn(currentUser); + + assertFalse(userService.selfFollowing(userToCheck)); + } + + @Test + void selfFollowing_ShouldReturnFalse_WhenUnauthorizedAccessExceptionOccurs() throws UnauthorizedAccessException { + User userToCheck = new User(); + + when(userContextService.getCurrentUser()).thenThrow(UnauthorizedAccessException.class); + + assertFalse(userService.selfFollowing(userToCheck)); + } + + @Test + void getFollowers_ShouldReturnFollowersList() { + User user = new User(); + user.setId(41L); + + User follower1 = new User(); + follower1.setId(1L); + follower1.setUsername("username1"); + follower1.setEmail("email1"); + follower1.setFirstName("Follower1"); + + User follower2 = new User(); + follower2.setId(2L); + follower2.setUsername("username2"); + follower2.setEmail("email2"); + follower2.setFirstName("Follower2"); + + user.getFollowers().add(follower1); + user.getFollowers().add(follower2); + + List followers = userService.getFollowers(user); + + assertEquals(2, followers.size()); + assertTrue(followers.contains(follower1)); + assertTrue(followers.contains(follower2)); + } +} From 5a42f5fa335c8fc485f3d4c90c3f15121784c9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atakan=20Ya=C5=9Far?= Date: Tue, 19 Nov 2024 16:24:26 +0200 Subject: [PATCH 4/7] feat(moble): implement question creation screen --- mobile/app/(tabs)/search.tsx | 2 +- mobile/app/question/new.tsx | 119 +++++++++++++++++++++++++++ mobile/app/tags/[tagId].tsx | 2 +- mobile/components/ui/input/index.tsx | 17 ++-- 4 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 mobile/app/question/new.tsx diff --git a/mobile/app/(tabs)/search.tsx b/mobile/app/(tabs)/search.tsx index 170f1d8c..6bd8a416 100644 --- a/mobile/app/(tabs)/search.tsx +++ b/mobile/app/(tabs)/search.tsx @@ -42,7 +42,7 @@ export default function SearchScreen() { const tags = (searchResult?.data as { items?: TagSummary[] })?.items || []; return ( - + (); + const { mutateAsync: createQuestion, status, error } = useCreateQuestion(); + const router = useRouter(); + + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + + const token = useAuthStore((state) => state.token); + + if (!token) { + return ( + + ); + } + + const handleSubmit = async () => { + if (!title || !content || !tagId) { + alert("All fields are required."); + return; + } + + try { + console.log("Creating question..."); + console.log("Title:", title); + console.log("Content:", content); + console.log("Tag ID:", tagId); + + await createQuestion({ + body: { title, content, tags: [tagId] }, + }); + alert("Question created successfully!"); + router.push(`/tags/${tagId}`); + } catch (e) { + console.error("Failed to create question:", e); + } + }; + + if (status === "pending") { + return ; + } + + return ( + + + Create New Question + + {error && } + + {/* Title Input */} + + Title + + + + + + {/* Content Input */} + + Content + + + + + + {/* Submit Button */} + + + + + + ); +} diff --git a/mobile/app/tags/[tagId].tsx b/mobile/app/tags/[tagId].tsx index a777f6de..d64b7323 100644 --- a/mobile/app/tags/[tagId].tsx +++ b/mobile/app/tags/[tagId].tsx @@ -105,7 +105,7 @@ export default function TagPage() { Questions {!!token && ( - +