From f52f5f36a4462201409b081851d5615f55af1aad Mon Sep 17 00:00:00 2001 From: Tokishu Date: Fri, 23 May 2025 21:58:00 +0200 Subject: [PATCH] Migrated to DB use --- pom.xml | 41 ++++ .../note/controller/NotesController.java | 35 ++-- .../net/tokishu/note/dto/NoteRequest.java | 9 + .../java/net/tokishu/note/model/Note.java | 52 ++--- .../net/tokishu/note/repo/NoteRepository.java | 8 + .../net/tokishu/note/service/NoteService.java | 56 ++--- src/main/resources/application.properties | 11 +- .../tokishu/note/service/NoteServiceTest.java | 191 +++++++++++++++--- .../resources/application-test.properties | 24 +++ 9 files changed, 319 insertions(+), 108 deletions(-) create mode 100644 src/main/java/net/tokishu/note/dto/NoteRequest.java create mode 100644 src/main/java/net/tokishu/note/repo/NoteRepository.java create mode 100644 src/test/resources/application-test.properties diff --git a/pom.xml b/pom.xml index 087afdf..1a49bf4 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,34 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + 1.18.38 + provided + + + org.springframework.data + spring-data-jpa + @@ -54,6 +82,19 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + -XX:+EnableDynamicAgentLoading + -Djdk.instrument.traceUsage=false + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + + + diff --git a/src/main/java/net/tokishu/note/controller/NotesController.java b/src/main/java/net/tokishu/note/controller/NotesController.java index 17d34e8..d417e56 100644 --- a/src/main/java/net/tokishu/note/controller/NotesController.java +++ b/src/main/java/net/tokishu/note/controller/NotesController.java @@ -1,11 +1,17 @@ package net.tokishu.note.controller; +import net.tokishu.note.dto.NoteRequest; import net.tokishu.note.model.Note; import net.tokishu.note.service.NoteService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; @RestController public class NotesController { @@ -14,27 +20,30 @@ public class NotesController { private NoteService noteService; @GetMapping("/notes") - public List getAll(){ - return noteService.getAll(); + public ResponseEntity getAll(){ + return ResponseEntity.ok(noteService.getAll()); } - @GetMapping("/note/{id}") - public Note find(@PathVariable Long id){ - return noteService.find(id); + @GetMapping("/note/{uuid}") + public Note find(@PathVariable UUID uuid){ + return noteService.find(uuid); } @PostMapping("/note") - public Note add(@RequestBody Note note){ - return noteService.add(note); + public ResponseEntity add(@RequestBody NoteRequest note){ + noteService.add(note); + return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Note added")); } - @PutMapping("/note/{id}") - public Note update(@PathVariable Long id, @RequestBody Note note){ - return noteService.update(id, note); + @PutMapping("/note/{uuid}") + public ResponseEntity update(@PathVariable("uuid") UUID uuid, @RequestBody NoteRequest note){ + noteService.update(uuid, note); + return ResponseEntity.ok(Map.of("message", "Note updated")); } - @DeleteMapping("/note/{id}") - public void delete(@PathVariable Long id){ - noteService.delete(id); + @DeleteMapping("/note/{uuid}") + public ResponseEntity delete(@PathVariable UUID uuid){ + noteService.delete(uuid); + return ResponseEntity.ok(Map.of(" message", "Note deleted")); } } diff --git a/src/main/java/net/tokishu/note/dto/NoteRequest.java b/src/main/java/net/tokishu/note/dto/NoteRequest.java new file mode 100644 index 0000000..83a1cc8 --- /dev/null +++ b/src/main/java/net/tokishu/note/dto/NoteRequest.java @@ -0,0 +1,9 @@ +package net.tokishu.note.dto; + +import lombok.Data; + +@Data +public class NoteRequest { + private String name; + private String text; +} diff --git a/src/main/java/net/tokishu/note/model/Note.java b/src/main/java/net/tokishu/note/model/Note.java index d14f3ba..514ec75 100644 --- a/src/main/java/net/tokishu/note/model/Note.java +++ b/src/main/java/net/tokishu/note/model/Note.java @@ -1,46 +1,18 @@ package net.tokishu.note.model; +import jakarta.persistence.*; +import lombok.Data; +import org.springframework.validation.annotation.Validated; + +import java.util.UUID; + +@Entity +@Data +@Table(name = "Notes") public class Note { - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID uuid; private String name; private String text; - - public Note(Long id, String name, String text) { - this.id = id; - this.name = name; - this.text = text; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } - - @Override - public String toString() { - return "Note{" + - "id=" + id + - ", name='" + name + '\'' + - ", text='" + text + '\'' + - '}'; - } } \ No newline at end of file diff --git a/src/main/java/net/tokishu/note/repo/NoteRepository.java b/src/main/java/net/tokishu/note/repo/NoteRepository.java new file mode 100644 index 0000000..c13f9ab --- /dev/null +++ b/src/main/java/net/tokishu/note/repo/NoteRepository.java @@ -0,0 +1,8 @@ +package net.tokishu.note.repo; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface NoteRepository extends JpaRepository { +} diff --git a/src/main/java/net/tokishu/note/service/NoteService.java b/src/main/java/net/tokishu/note/service/NoteService.java index cc6dcce..5bff837 100644 --- a/src/main/java/net/tokishu/note/service/NoteService.java +++ b/src/main/java/net/tokishu/note/service/NoteService.java @@ -1,52 +1,52 @@ package net.tokishu.note.service; +import lombok.RequiredArgsConstructor; +import net.tokishu.note.dto.NoteRequest; import net.tokishu.note.model.Note; +import net.tokishu.note.repo.NoteRepository; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; @Service +@RequiredArgsConstructor public class NoteService { - private List notes = new ArrayList<>(); - private AtomicLong idGenerator = new AtomicLong(1); + + public final NoteRepository noteRepository; public List getAll(){ - return notes; + return noteRepository.findAll(); } - public Note find(Long id) { - for (Note note : notes) { - if (note.getId().equals(id)) { - return note; - } - } - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"); + public Note find(UUID uuid) { + return noteRepository.findById(uuid) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found")); } - public Note add(Note note){ - note.setId(idGenerator.getAndIncrement()); - notes.add(note); - return note; + public Note add(NoteRequest data){ + Note note = new Note(); + note.setName(data.getName()); + note.setText(data.getText()); + + return noteRepository.save(note); } - public Note update(Long id, Note updatedNote){ - for (Note note : notes){ - if (note.getId().equals(id)) { - note.setName(updatedNote.getName()); - note.setText(updatedNote.getText()); - return note; - } - } - throw new NoSuchElementException("Note not found"); + public Note update(UUID uuid, NoteRequest data){ + Note existing = noteRepository.findById(uuid) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found")); + existing.setName(data.getName()); + existing.setText(data.getText()); + + return noteRepository.save(existing); } - public void delete(Long id){ - notes.removeIf(note -> note.getId().equals(id)); + public void delete(UUID uuid){ + Note existing = noteRepository.findById(uuid) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found")); + noteRepository.deleteById(uuid); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 03d0d49..a539af7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,11 @@ spring.application.name=notes -spring.mvc.servlet.path=/api \ No newline at end of file +spring.mvc.servlet.path=/api + +# DB +spring.datasource.url=jdbc:postgresql://localhost:5432/notes +spring.datasource.username=postgres +spring.datasource.password=password +spring.jpa.hibernate.ddl-auto=update + +# Disable trace +server.error.include-stacktrace=never \ No newline at end of file diff --git a/src/test/java/net/tokishu/note/service/NoteServiceTest.java b/src/test/java/net/tokishu/note/service/NoteServiceTest.java index df32cb2..bffaa87 100644 --- a/src/test/java/net/tokishu/note/service/NoteServiceTest.java +++ b/src/test/java/net/tokishu/note/service/NoteServiceTest.java @@ -1,63 +1,202 @@ package net.tokishu.note.service; +import net.tokishu.note.dto.NoteRequest; import net.tokishu.note.model.Note; +import net.tokishu.note.repo.NoteRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; import java.util.List; +import java.util.UUID; + import static org.junit.jupiter.api.Assertions.*; +@SpringBootTest +@ActiveProfiles("test") // Используем тестовый профиль +@Transactional // Откатывает транзакции после каждого теста public class NoteServiceTest { + @Autowired private NoteService noteService; + @Autowired + private NoteRepository noteRepository; + @BeforeEach - void setUp(){ - noteService = new NoteService(); + void setUp() { + // Очищаем БД перед каждым тестом + noteRepository.deleteAll(); } @Test - void addAndGetAllNotes(){ - Note note = new Note(1L, "QQ", "Test content"); - noteService.add(note); + void addAndGetAllNotes() { + // Given + NoteRequest noteRequest = new NoteRequest(); + noteRequest.setName("Test Note"); + noteRequest.setText("Test content"); + // When + Note savedNote = noteService.add(noteRequest); List notes = noteService.getAll(); + + // Then + assertNotNull(savedNote); + assertNotNull(savedNote.getUuid()); assertEquals(1, notes.size()); - assertEquals("QQ", notes.get(0).getName()); + assertEquals("Test Note", notes.get(0).getName()); + assertEquals("Test content", notes.get(0).getText()); } @Test - void updateNote(){ - Note note = new Note(1L, "Old QQ", "Old test content"); - noteService.add(note); + void findExistingNote() { + // Given + NoteRequest noteRequest = new NoteRequest(); + noteRequest.setName("Find Me"); + noteRequest.setText("Find content"); + Note savedNote = noteService.add(noteRequest); - Note updatedNote = new Note(1L, "New day, new qq :3", "New test content"); - noteService.update(1L, updatedNote); + // When + Note foundNote = noteService.find(savedNote.getUuid()); - Note result = noteService.getAll().get(0); - assertEquals("New day, new qq :3", result.getName()); + // Then + assertNotNull(foundNote); + assertEquals("Find Me", foundNote.getName()); + assertEquals("Find content", foundNote.getText()); + assertEquals(savedNote.getUuid(), foundNote.getUuid()); } @Test - void deleteNote(){ - Note noteToDelete = new Note(1L, "Bye bye :(", "content"); - noteService.add(noteToDelete); - noteService.delete(1L); + void findNonExistingNote_ShouldThrowException() { + // Given + UUID nonExistingId = UUID.randomUUID(); - assertTrue(noteService.getAll().isEmpty()); + // When & Then + ResponseStatusException exception = assertThrows( + ResponseStatusException.class, + () -> noteService.find(nonExistingId) + ); + + assertTrue(exception.getMessage().contains("Note not found")); } @Test - void findNote(){ + void updateExistingNote() { + // Given + NoteRequest originalRequest = new NoteRequest(); + originalRequest.setName("Original Name"); + originalRequest.setText("Original content"); + Note savedNote = noteService.add(originalRequest); - for (long i = 1; i <= 10; i++) { - Note note = new Note(i, "Note " + i, "Content for note №" + i); - noteService.add(note); + NoteRequest updateRequest = new NoteRequest(); + updateRequest.setName("Updated Name"); + updateRequest.setText("Updated content"); + + // When + Note updatedNote = noteService.update(savedNote.getUuid(), updateRequest); + + // Then + assertNotNull(updatedNote); + assertEquals(savedNote.getUuid(), updatedNote.getUuid()); + assertEquals("Updated Name", updatedNote.getName()); + assertEquals("Updated content", updatedNote.getText()); + + // Проверяем, что изменения сохранились в БД + Note foundNote = noteService.find(savedNote.getUuid()); + assertEquals("Updated Name", foundNote.getName()); + assertEquals("Updated content", foundNote.getText()); + } + + @Test + void updateNonExistingNote_ShouldThrowException() { + // Given + UUID nonExistingId = UUID.randomUUID(); + NoteRequest updateRequest = new NoteRequest(); + updateRequest.setName("Update Name"); + updateRequest.setText("Update content"); + + // When & Then + ResponseStatusException exception = assertThrows( + ResponseStatusException.class, + () -> noteService.update(nonExistingId, updateRequest) + ); + + assertTrue(exception.getMessage().contains("Note not found")); + } + + @Test + void deleteExistingNote() { + // Given + NoteRequest noteRequest = new NoteRequest(); + noteRequest.setName("Delete Me"); + noteRequest.setText("Delete content"); + Note savedNote = noteService.add(noteRequest); + + // When + assertDoesNotThrow(() -> noteService.delete(savedNote.getUuid())); + + // Then + List notes = noteService.getAll(); + assertTrue(notes.isEmpty()); + + // Проверяем, что заметка действительно удалена + ResponseStatusException exception = assertThrows( + ResponseStatusException.class, + () -> noteService.find(savedNote.getUuid()) + ); + assertTrue(exception.getMessage().contains("Note not found")); + } + + @Test + void deleteNonExistingNote_ShouldThrowException() { + // Given + UUID nonExistingId = UUID.randomUUID(); + + // When & Then + ResponseStatusException exception = assertThrows( + ResponseStatusException.class, + () -> noteService.delete(nonExistingId) + ); + + assertTrue(exception.getMessage().contains("Note not found")); + } + + @Test + void getAllNotes_WhenEmpty() { + // When + List notes = noteService.getAll(); + + // Then + assertNotNull(notes); + assertTrue(notes.isEmpty()); + } + + @Test + void getAllNotes_WithMultipleNotes() { + // Given + for (int i = 1; i <= 5; i++) { + NoteRequest noteRequest = new NoteRequest(); + noteRequest.setName("Note " + i); + noteRequest.setText("Content for note " + i); + noteService.add(noteRequest); } - Note result = noteService.find(5L); + // When + List notes = noteService.getAll(); - assertNotNull(result, "Note should be found"); - assertEquals("Note 5", result.getName()); + // Then + assertNotNull(notes); + assertEquals(5, notes.size()); + + // Проверяем, что все заметки уникальны по ID + long uniqueIds = notes.stream() + .map(Note::getUuid) + .distinct() + .count(); + assertEquals(5, uniqueIds); } -} +} \ No newline at end of file diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..d314aea --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,24 @@ +# Helped by ClaudeAI :3 + +# ???????? ???????????? +spring.application.name=notes-test + +# ?????????? H2 ? ?????? ??? ?????? (??????? ? ????????????) +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +# JPA ????????? ??? ?????? +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.open-in-view=false + +# ????????? ???? ??? ?????? +logging.level.org.springframework=WARN +logging.level.org.hibernate=WARN +logging.level.com.zaxxer.hikari=WARN + +# ????????? ???????? ???????????????? ??? ?????? +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfigurationty.servlet.SecurityAutoConfiguration \ No newline at end of file