-
Notifications
You must be signed in to change notification settings - Fork 0
feature/scaffold book api and createBook service logic and tests #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
c36920b
Add package declaration to BookApplicationTests
codesungrape ce18bad
Add test setup for BookService with mocked repository
codesungrape 98ef185
Add test for createBook success
codesungrape c9c366b
Add test for blank title edge case
codesungrape bbee690
Add test for null/invalid input tests
codesungrape 1a78f63
Add NullPointerException when entire request is null
codesungrape dc7b207
Add BookService.java with createBook() and validation; TDD tests passing
codesungrape 36e31f0
Add basic scaffolding for book request, book entity and book repository
codesungrape 314cfeb
add failing tests for repository failures
codesungrape f5aec79
Add defensive check for learning purposes
codesungrape 5e298ee
Add clearer comments
codesungrape 624be2d
Add edgecase tests: longtitle, longSynopsis, specialChars in title
codesungrape 1b5fa56
Refactor test to use @ParameterizedTest/CsvSource + update build.gradle
codesungrape 0176bec
test: use @MethodSource to pass objects instead of primitives
codesungrape 1e35f74
Clean up unused imports and comments
codesungrape 8f669dc
fix(entity): Correct timestamp and builder defaults in Book
codesungrape 9a294c6
Leave comment about redundant validation check
codesungrape 0ee37ed
Clean up comments
codesungrape e4da445
Clean up redundant comment
codesungrape 621eaef
Remove redundant SpringBoot config from openapi.yml
codesungrape 3ecf354
Remove commented out code for redability and maintainability
codesungrape db2e5fe
Add comments for Test error to fix in next PR
codesungrape File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/java/com/codesungrape/hmcts/BookAPI/BookApiApplication.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.codesungrape.hmcts.BookAPI; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
|
||
| @SpringBootApplication | ||
| public class BookApiApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.run(BookApiApplication.class, args); | ||
| } | ||
|
|
||
| } |
27 changes: 27 additions & 0 deletions
27
src/main/java/com/codesungrape/hmcts/BookAPI/dto/BookRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.codesungrape.hmcts.BookAPI.dto; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Value; | ||
|
|
||
| /** | ||
| * DTO representing the required input for creating or replacing a Book resource. | ||
| * This class mirrors the OpenAPI 'BookInput' schema. | ||
| * @Value: Makes all fields 'final' (immutable), generates constructor, getters, and equals/hashCode/toString. | ||
| * @NotBlank: Enforces the required status from your OpenAPI schema. If the field is missing or an empty string, Spring will return a 400 Bad Request. | ||
| * @JsonProperty: Jackson library - maps snake_case JSON (HMCTS rules) to camelCase Java | ||
| */ | ||
| @Value | ||
| public class BookRequest { | ||
| @NotBlank(message= "Title is required") | ||
| @JsonProperty("title") | ||
| String title; | ||
|
|
||
| @NotBlank(message = "Synopsis is required") | ||
| @JsonProperty("synopsis") | ||
| String synopsis; | ||
|
|
||
| @NotBlank(message = "Author is required") | ||
| @JsonProperty("author") | ||
| String author; | ||
| } |
76 changes: 76 additions & 0 deletions
76
src/main/java/com/codesungrape/hmcts/BookAPI/entity/Book.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| package com.codesungrape.hmcts.BookAPI.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import java.util.UUID; | ||
| import lombok.*; | ||
| import java.time.Instant; | ||
|
|
||
| /** | ||
| * JPA Entity representing the Book table in PostgreSQL. | ||
| * This holds the persisted state of the resource. | ||
| * HMCTS Rule Check: IDs must be opaque strings. Using UUID for distributed ID generation. | ||
| * @Entity: Marks the class as a JPA entity - tells hibernate to map Java classes to database tables. | ||
| * @Table: Defines which database table this entity maps to. HMCTS Naming: Lowercase, singular table name is common practice. | ||
| * Lombok annotations: | ||
| * @Getter: Automatically generates getters for all fields. | ||
| * @Setter: Automatically generates setters. | ||
| * @AllArgsConstructor: Generates a no-argument constructor (required by JPA). | ||
| * JPA needs to instantiate the entity using reflection. 'PROTECTED' prevents misuse. | ||
| * @Builder: Adds a builder pattern for clean object creation. | ||
| * You can do Book.builder().title("A").author("B").build(); | ||
| */ | ||
| @Entity | ||
| @Table(name = "book") | ||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) // For JPA/Hibernate requirements | ||
| @AllArgsConstructor // For easy construction in tests | ||
| @Builder // For convenience in creating instances | ||
| public class Book { | ||
|
|
||
| @Id // Primary key of the table | ||
| @GeneratedValue(strategy = GenerationType.UUID) | ||
| @Column(name = "id", nullable = false) // maps the field to a database column named 'id' + 'nullable =false' database column cannot be NULL. | ||
| private UUID id; | ||
|
|
||
| @Column(name = "title", nullable = false) | ||
| private String title; | ||
|
|
||
| @Column(name = "synopsis", nullable = false, columnDefinition = "TEXT") | ||
| private String synopsis; | ||
|
|
||
| @Column(name = "author", nullable = false) | ||
| private String author; | ||
|
|
||
| // Soft delete - makes DELETE operations idempotent (safe to repeat) | ||
| // Soft delete - using @Builder.Default to ensure the builder | ||
| // respects this initialization if the field is not set explicitly. | ||
| @Column(name = "deleted", nullable = false) | ||
| @Builder.Default | ||
| private boolean deleted = false; | ||
|
|
||
| // `createdAt` is null upon object creation. | ||
| // It will be set by the `onCreate()` method right before persistence. | ||
| @Column(name = "created_at", nullable = false, updatable = false) | ||
| private Instant createdAt; | ||
|
|
||
| @Column(name = "modified_at") | ||
| private java.time.Instant modifiedAt; | ||
|
|
||
| // --- JPA lifecycle callbacks --- | ||
| @PrePersist | ||
| protected void onCreate() { | ||
| this.createdAt = java.time.Instant.now(); | ||
| } | ||
|
|
||
| // --- Business Logic Helper --- | ||
| // HMCTS mandates business logic in services, but a setter hook is acceptable. | ||
| // Lifecycle callback - special method runs automatically before Hibernate updates a record in the database. | ||
| @PreUpdate | ||
| protected void onUpdate() { | ||
|
|
||
| this.modifiedAt = java.time.Instant.now(); | ||
| } | ||
|
|
||
|
|
||
| } |
24 changes: 24 additions & 0 deletions
24
src/main/java/com/codesungrape/hmcts/BookAPI/repository/BookRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.codesungrape.hmcts.BookAPI.repository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
|
|
||
| import com.codesungrape.hmcts.BookAPI.entity.Book; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| /** | ||
| * Repository interface for Book Entity. | ||
| * Spring Data JPA automatically provides CRUD operations based on the Entity and ID type. | ||
| */ | ||
| @Repository | ||
| public interface BookRepository extends JpaRepository<Book, UUID> { | ||
|
|
||
| // Custom query to find books that have NOT been soft-deleted | ||
| List<Book> findAllByDeletedFalse(); | ||
|
|
||
| // Custom query to find a specific, non-deleted book by ID. | ||
| Optional<Book> findByIdAndDeleteFalse(UUID id); | ||
|
|
||
| } |
56 changes: 56 additions & 0 deletions
56
src/main/java/com/codesungrape/hmcts/BookAPI/service/BookService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.codesungrape.hmcts.BookAPI.service; | ||
|
|
||
| import com.codesungrape.hmcts.BookAPI.dto.BookRequest; | ||
| import com.codesungrape.hmcts.BookAPI.repository.BookRepository; | ||
| import org.springframework.stereotype.Service; // Marks a class as a Service Layer component. | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| import com.codesungrape.hmcts.BookAPI.entity.Book; | ||
|
|
||
| /** | ||
| * Service layer responsible for all business logic related to the Book resource. | ||
| */ | ||
| @Service | ||
| @RequiredArgsConstructor // Lombok creates constructor for dependency injection | ||
| public class BookService { | ||
|
|
||
| // Create a field to store the repo | ||
| private final BookRepository bookRepository; | ||
|
|
||
| // 1. CREATE Operation (POST /books) | ||
| public Book createBook(BookRequest request) { | ||
| // Validation check for business rules (e.g., uniqueness, if required) | ||
| if (request == null) { | ||
| throw new NullPointerException("BookRequest cannot be null"); | ||
| } | ||
|
|
||
|
|
||
| // REVISIT: Leaving this here for now as i haven't implemented the Controller Layer yet | ||
| // The service layer is duplicating validation that already exists in the | ||
| // BookRequest DTO with @notblank annotations. Since the DTO has validation | ||
| // constraints, this manual check is redundant when Spring's validation | ||
| // framework is properly configured in the controller layer. | ||
| // Consider removing this duplication or adding a comment explaining | ||
| // why service-level validation is necessary in addition to DTO validation. | ||
| if (request.getTitle() == null || request.getTitle().isBlank()) { | ||
| throw new IllegalArgumentException("Book title cannot be null or blank"); | ||
| } | ||
|
|
||
| // Map DTO to Entity | ||
| Book newBook = Book.builder() | ||
| .title(request.getTitle()) | ||
| .author(request.getAuthor()) | ||
| .synopsis(request.getSynopsis()) | ||
| // ID and created_at are auto-generated by JPA/DB | ||
| .build(); | ||
|
|
||
| Book savedBook = bookRepository.save(newBook); | ||
|
|
||
| // Defensive check (even though it "shouldn't" happen aka follows JPA contract) | ||
| if (savedBook == null) { | ||
| throw new IllegalStateException("Failed to save book - repository returned null"); | ||
| } | ||
|
|
||
| return savedBook; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| spring: | ||
| application: | ||
| name: BookAPI |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.