Skip to content

Conversation

juancaCeb
Copy link

Issue Number

Purpose

  • The attribute masteryScore need a validation for only having values between 0.0 and 1.0

Technical Details

Used Jakarta Bean Validation Annotation.

Screenshot 2025-05-29 at 2 55 25 p m

@juancaCeb juancaCeb requested a review from a team as a code owner May 29, 2025 20:56
@juancaCeb juancaCeb requested review from alexander-kuruvilla, eymaal and vrudas and removed request for a team May 29, 2025 20:56
Copy link
Contributor

coderabbitai bot commented May 29, 2025

Walkthrough

Validation constraints were added to the masteryScore field of the LetterSoundAssessmentEvent entity. The field is now annotated to enforce that its value remains within the range of 0 to 1 inclusive, using @Min(0) and @Max(1) annotations, along with corresponding validation messages.

Changes

File(s) Change Summary
src/main/java/ai/elimu/entity/analytics/LetterSoundAssessmentEvent.java Added @Min(0) and @Max(1) validation annotations to masteryScore field with custom messages.

Assessment against linked issues

Objective Addressed Explanation
Add min/max validation constraint to LetterSoundAssessmentEvent (#2198) Used @Min/@Max instead of @DecimalMin/@DecimalMax as specified in the issue.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca8c7a1 and 5b5672c.

📒 Files selected for processing (1)
  • src/main/java/ai/elimu/entity/analytics/LetterSoundAssessmentEvent.java (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: build (windows-latest, 21)
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: build (macos-latest, 21)
  • GitHub Check: build (macos-latest, 17)
  • GitHub Check: build (ubuntu-latest, 17)
  • GitHub Check: build (windows-latest, 17)
  • GitHub Check: build (ubuntu-latest, 21)
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
🔇 Additional comments (2)
src/main/java/ai/elimu/entity/analytics/LetterSoundAssessmentEvent.java (2)

4-5: LGTM! Correct imports for Jakarta Bean Validation.

The imports for @Min and @Max annotations are correctly added from the Jakarta Bean Validation API, which is the appropriate choice for validation constraints.


34-36: LGTM! Validation constraints correctly implement the required range.

The validation annotations properly enforce the documented range [0.0, 1.0] for the masteryScore field:

  • @Min(value = 0) ensures values are not below 0
  • @Max(value = 1) ensures values do not exceed 1
  • Error messages are clear and user-friendly

The implementation correctly addresses the PR objective to validate that masteryScore only accepts values between 0.0 and 1.0.

Note: The annotations will allow null values to pass validation. If null values should be rejected, consider adding @NotNull annotation as well, but this appears to be intentional based on the field documentation mentioning it "might not be included."

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented May 29, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 14.49%. Comparing base (105acb1) to head (5b5672c).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##               main    #2200   +/-   ##
=========================================
  Coverage     14.49%   14.49%           
  Complexity      403      403           
=========================================
  Files           243      243           
  Lines          6588     6588           
  Branches        757      757           
=========================================
  Hits            955      955           
  Misses         5582     5582           
  Partials         51       51           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jo-elimu
Copy link
Member

@coderabbitai Can you suggest a unit test for testing the code changes in this PR?

Copy link
Contributor

coderabbitai bot commented May 30, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@jo-elimu Here's a comprehensive unit test suggestion for testing the validation constraints on the masteryScore field:

package ai.elimu.entity.analytics;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class LetterSoundAssessmentEventValidationTest {

    private Validator validator;
    private LetterSoundAssessmentEvent event;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
        
        // Create a basic valid event for testing
        event = new LetterSoundAssessmentEvent();
        // Set other required fields if any...
    }

    @Test
    @DisplayName("Valid mastery score values should pass validation")
    void testValidMasteryScores() {
        // Test boundary values and typical valid values
        float[] validScores = {0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.999f, 0.001f};
        
        for (float score : validScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            assertTrue(violations.isEmpty(), 
                String.format("Mastery score %.3f should be valid but validation failed", score));
        }
    }

    @Test
    @DisplayName("Mastery score below minimum should fail validation")
    void testMasteryScoreBelowMinimum() {
        float[] invalidScores = {-0.1f, -1.0f, -0.001f, Float.NEGATIVE_INFINITY};
        
        for (float score : invalidScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            assertFalse(violations.isEmpty(), 
                String.format("Mastery score %.3f should be invalid", score));
            
            // Verify the specific violation message
            boolean foundMinViolation = violations.stream()
                .anyMatch(v -> v.getMessage().contains("Mastery score must be at least 0"));
            assertTrue(foundMinViolation, 
                String.format("Expected minimum validation message for score %.3f", score));
        }
    }

    @Test
    @DisplayName("Mastery score above maximum should fail validation")
    void testMasteryScoreAboveMaximum() {
        float[] invalidScores = {1.1f, 2.0f, 1.001f, Float.POSITIVE_INFINITY};
        
        for (float score : invalidScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            assertFalse(violations.isEmpty(), 
                String.format("Mastery score %.3f should be invalid", score));
            
            // Verify the specific violation message
            boolean foundMaxViolation = violations.stream()
                .anyMatch(v -> v.getMessage().contains("Mastery score must not exceed 1"));
            assertTrue(foundMaxViolation, 
                String.format("Expected maximum validation message for score %.3f", score));
        }
    }

    @Test
    @DisplayName("Null mastery score should pass validation")
    void testNullMasteryScore() {
        event.setMasteryScore(null);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        
        // @Min and @Max allow null values by default
        assertTrue(violations.isEmpty(), 
            "Null mastery score should be valid as it might not be included");
    }

    @Test
    @DisplayName("Boundary values should be properly validated")
    void testBoundaryValues() {
        // Test exact boundary values
        event.setMasteryScore(0.0f);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        assertTrue(violations.isEmpty(), "Mastery score of 0.0 should be valid");

        event.setMasteryScore(1.0f);
        violations = validator.validate(event);
        assertTrue(violations.isEmpty(), "Mastery score of 1.0 should be valid");
    }
}

This test suite covers:

  1. Valid values: Tests various valid scores including boundaries (0.0, 1.0) and typical values
  2. Below minimum: Tests negative values and ensures proper error messages
  3. Above maximum: Tests values greater than 1 and ensures proper error messages
  4. Null handling: Verifies that null values are allowed (as per Jakarta Bean Validation default behavior)
  5. Boundary conditions: Explicitly tests the exact boundary values
  6. Error message validation: Ensures the custom validation messages are properly triggered

The test uses Jakarta Bean Validation's Validator to test the constraints directly on the entity, which is the most accurate way to test validation annotations.


🏁 Script executed:

#!/bin/bash
# First, let's examine the actual file to understand the entity structure
cat src/main/java/ai/elimu/entity/analytics/LetterSoundAssessmentEvent.java

Length of output: 1312


🏁 Script executed:

#!/bin/bash
# Look for existing test files for this entity or similar analytics entities
fd -t f -e java "*LetterSoundAssessmentEvent*Test*" src/test/
fd -t f -e java "*AssessmentEvent*Test*" src/test/

Length of output: 731


🏁 Script executed:

#!/bin/bash
# Search for existing validation test patterns in the codebase
rg -A 10 -B 5 "@Test.*validation|@Test.*constraint|@Test.*valid" src/test/ --type java | head -50

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Look for validation test utilities or patterns
rg -A 5 "Validator|ValidatorFactory|validate" src/test/ --type java | head -30

Length of output: 80


Based on my analysis of the LetterSoundAssessmentEvent entity, here's a comprehensive unit test suggestion for testing the validation constraints on the masteryScore field:

package ai.elimu.entity.analytics;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class LetterSoundAssessmentEventValidationTest {

    private Validator validator;
    private LetterSoundAssessmentEvent event;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
        
        // Create a basic valid event for testing
        event = new LetterSoundAssessmentEvent();
        event.setLetterSoundLetters("sh");
        event.setLetterSoundSounds("ʃ");
        // Set other fields as needed for a valid base object
    }

    @Test
    @DisplayName("Valid mastery score values should pass validation")
    void testValidMasteryScores() {
        // Test boundary values and typical valid values
        float[] validScores = {0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.999f, 0.001f};
        
        for (float score : validScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            // Filter only masteryScore violations for this test
            long masteryScoreViolations = violations.stream()
                .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
                .count();
                
            assertEquals(0, masteryScoreViolations, 
                String.format("Mastery score %.3f should be valid but validation failed", score));
        }
    }

    @Test
    @DisplayName("Mastery score below minimum should fail validation")
    void testMasteryScoreBelowMinimum() {
        float[] invalidScores = {-0.1f, -1.0f, -0.001f};
        
        for (float score : invalidScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            // Check for masteryScore violations specifically
            boolean foundMinViolation = violations.stream()
                .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
                .anyMatch(v -> v.getMessage().contains("Mastery score must be at least 0"));
                
            assertTrue(foundMinViolation, 
                String.format("Expected minimum validation violation for score %.3f", score));
        }
    }

    @Test
    @DisplayName("Mastery score above maximum should fail validation")
    void testMasteryScoreAboveMaximum() {
        float[] invalidScores = {1.1f, 2.0f, 1.001f};
        
        for (float score : invalidScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            // Check for masteryScore violations specifically
            boolean foundMaxViolation = violations.stream()
                .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
                .anyMatch(v -> v.getMessage().contains("Mastery score must not exceed 1"));
                
            assertTrue(foundMaxViolation, 
                String.format("Expected maximum validation violation for score %.3f", score));
        }
    }

    @Test
    @DisplayName("Null mastery score should pass validation")
    void testNullMasteryScore() {
        event.setMasteryScore(null);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        
        // Check that no masteryScore violations exist
        long masteryScoreViolations = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .count();
            
        assertEquals(0, masteryScoreViolations, 
            "Null mastery score should be valid as the field documentation states it might not be included");
    }

    @Test
    @DisplayName("Boundary values should be properly validated")
    void testBoundaryValues() {
        // Test exact boundary values
        event.setMasteryScore(0.0f);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        long masteryScoreViolations = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .count();
        assertEquals(0, masteryScoreViolations, "Mastery score of 0.0 should be valid");

        event.setMasteryScore(1.0f);
        violations = validator.validate(event);
        masteryScoreViolations = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .count();
        assertEquals(0, masteryScoreViolations, "Mastery score of 1.0 should be valid");
    }

    @Test
    @DisplayName("Validation messages should be correct")
    void testValidationMessages() {
        // Test minimum validation message
        event.setMasteryScore(-0.1f);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        
        ConstraintViolation<LetterSoundAssessmentEvent> minViolation = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .filter(v -> v.getMessage().contains("Mastery score must be at least 0"))
            .findFirst()
            .orElse(null);
            
        assertNotNull(minViolation, "Should have minimum validation violation");
        assertEquals("Mastery score must be at least 0", minViolation.getMessage());

        // Test maximum validation message
        event.setMasteryScore(1.1f);
        violations = validator.validate(event);
        
        ConstraintViolation<LetterSoundAssessmentEvent> maxViolation = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .filter(v -> v.getMessage().contains("Mastery score must not exceed 1"))
            .findFirst()
            .orElse(null);
            
        assertNotNull(maxViolation, "Should have maximum validation violation");
        assertEquals("Mastery score must not exceed 1", maxViolation.getMessage());
    }
}

Key Features of This Test Suite:

  1. Jakarta Validation: Uses the correct jakarta.validation package (not the older javax.validation)

  2. Comprehensive Coverage:

    • Valid values including boundaries (0.0, 1.0)
    • Invalid values below minimum
    • Invalid values above maximum
    • Null value handling
    • Exact boundary testing
    • Custom validation message verification
  3. Focused Testing: Filters violations to only check masteryScore field violations, avoiding interference from other potential validation constraints

  4. Real-world Scenarios: Tests fractional values that are likely in actual usage (0.25, 0.75, etc.)

  5. Message Validation: Explicitly tests that the custom error messages defined in the annotations are properly triggered

  6. Null Safety: Confirms that null values are allowed (which aligns with the field documentation stating it "might not be included")

This test suite will ensure the validation constraints work correctly and provide clear feedback when the validation fails.


🏁 Script executed:

#!/bin/bash
# Try to find test files with correct syntax
fd -g "*Test*.java" src/test/ | head -10

Length of output: 519


🏁 Script executed:

#!/bin/bash
# Look for existing test patterns and dependencies
rg -l "Test" src/test/ --type java | head -5 | xargs cat | head -50

Length of output: 1709


🏁 Script executed:

#!/bin/bash
# Check what testing framework is being used
rg -o "import.*junit|import.*mockito|import.*validation" src/test/ --type java | sort | uniq | head -10

Length of output: 893


🏁 Script executed:

#!/bin/bash
# Look for parent AssessmentEvent class to understand inheritance
cat src/main/java/ai/elimu/entity/analytics/AssessmentEvent.java

Length of output: 1590


Based on my analysis of the existing test patterns in your codebase, here's an updated and more appropriate unit test suggestion for the masteryScore validation:

package ai.elimu.entity.analytics;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import java.util.Calendar;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class LetterSoundAssessmentEventValidationTest {

    private Validator validator;
    private LetterSoundAssessmentEvent event;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
        
        // Create a valid event with required fields from parent AssessmentEvent
        event = new LetterSoundAssessmentEvent();
        event.setTimestamp(Calendar.getInstance());
        event.setAndroidId("1234567890abcdef");
        event.setPackageName("ai.elimu.soundcards");
        event.setLetterSoundLetters("sh");
        event.setLetterSoundSounds("ʃ");
    }

    @Test
    @DisplayName("Valid mastery score values should pass validation")
    void testValidMasteryScores() {
        // Test boundary values and typical valid values
        float[] validScores = {0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.999f, 0.001f};
        
        for (float score : validScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            // Filter only masteryScore violations for this test
            long masteryScoreViolations = violations.stream()
                .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
                .count();
                
            assertEquals(0, masteryScoreViolations, 
                String.format("Mastery score %.3f should be valid but validation failed", score));
        }
    }

    @Test
    @DisplayName("Mastery score below minimum should fail validation")
    void testMasteryScoreBelowMinimum() {
        float[] invalidScores = {-0.1f, -1.0f, -0.001f};
        
        for (float score : invalidScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            // Check for masteryScore violations specifically
            boolean foundMinViolation = violations.stream()
                .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
                .anyMatch(v -> v.getMessage().contains("Mastery score must be at least 0"));
                
            assertTrue(foundMinViolation, 
                String.format("Expected minimum validation violation for score %.3f", score));
        }
    }

    @Test
    @DisplayName("Mastery score above maximum should fail validation")
    void testMasteryScoreAboveMaximum() {
        float[] invalidScores = {1.1f, 2.0f, 1.001f};
        
        for (float score : invalidScores) {
            event.setMasteryScore(score);
            Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
            
            // Check for masteryScore violations specifically
            boolean foundMaxViolation = violations.stream()
                .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
                .anyMatch(v -> v.getMessage().contains("Mastery score must not exceed 1"));
                
            assertTrue(foundMaxViolation, 
                String.format("Expected maximum validation violation for score %.3f", score));
        }
    }

    @Test
    @DisplayName("Null mastery score should pass validation")
    void testNullMasteryScore() {
        event.setMasteryScore(null);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        
        // Check that no masteryScore violations exist
        long masteryScoreViolations = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .count();
            
        assertEquals(0, masteryScoreViolations, 
            "Null mastery score should be valid as the field documentation states it might not be included");
    }

    @Test
    @DisplayName("Validation messages should be correct")
    void testValidationMessages() {
        // Test minimum validation message
        event.setMasteryScore(-0.1f);
        Set<ConstraintViolation<LetterSoundAssessmentEvent>> violations = validator.validate(event);
        
        ConstraintViolation<LetterSoundAssessmentEvent> minViolation = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .filter(v -> v.getMessage().contains("Mastery score must be at least 0"))
            .findFirst()
            .orElse(null);
            
        assertNotNull(minViolation, "Should have minimum validation violation");
        assertEquals("Mastery score must be at least 0", minViolation.getMessage());

        // Test maximum validation message
        event.setMasteryScore(1.1f);
        violations = validator.validate(event);
        
        ConstraintViolation<LetterSoundAssessmentEvent> maxViolation = violations.stream()
            .filter(v -> "masteryScore".equals(v.getPropertyPath().toString()))
            .filter(v -> v.getMessage().contains("Mastery score must not exceed 1"))
            .findFirst()
            .orElse(null);
            
        assertNotNull(maxViolation, "Should have maximum validation violation");
        assertEquals("Mastery score must not exceed 1", maxViolation.getMessage());
    }
}

Key Updates Based on Your Codebase:

  1. JUnit Jupiter: Uses the same JUnit Jupiter syntax (org.junit.jupiter.api.*) as your existing tests
  2. Required Parent Fields: Properly sets up the required @NotNull fields from AssessmentEvent parent class
  3. Jakarta Validation: Uses the correct Jakarta validation package
  4. Focused Testing: Only tests the new validation constraints, filtering out other potential violations

Test File Location:

Place this test at: src/test/java/ai/elimu/entity/analytics/LetterSoundAssessmentEventValidationTest.java

Required Dependencies:

Make sure your test dependencies include:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <scope>test</scope>
</dependency>

This test suite validates that your @Min(0) and @Max(1) constraints work correctly while being consistent with your existing test patterns and properly handling the entity's inheritance structure.

@jo-elimu
Copy link
Member

@juancaCeb Can you add a unit test for your code changes as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add min/max validation constraint to LetterSoundAssessmentEvent

2 participants