Skip to content

Conversation

@wlgns12370
Copy link
Contributor

@wlgns12370 wlgns12370 commented Aug 9, 2025

✨ 구현한 기능

📢 논의하고 싶은 내용

  • x

🎸 기타

  • x

Summary by CodeRabbit

  • New Features
    • Added processing for OAuth user information with automatic creation or update of user profiles based on login details.
  • Refactor
    • Streamlined OAuth user persistence logic for improved clarity and maintainability.
    • Consolidated Lombok dependencies and removed redundant Testcontainers dependencies for a cleaner build setup.
    • Enhanced user profile updates to include marketing agreement status.
  • Bug Fixes
    • Ensured correct handling of marketing agreement status when absent in service terms.
  • Tests
    • Introduced tests to verify accurate parsing of marketing consent from service terms responses.
  • New Database Support
    • Added comprehensive database schema with tables for applications, users, payments, inquiries, notifications, and more to support detailed application management.

@wlgns12370 wlgns12370 self-assigned this Aug 9, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The changes consolidate Lombok and Testcontainers dependencies in the Gradle build file, introduce a new OAuthUserPersistenceProcessor class for user persistence logic, refactor OAuthUserService to delegate persistence to this processor, update the UserJpaEntity method signature for marketing agreement, and add a test for marketing consent parsing.

Changes

Cohort / File(s) Change Summary
Gradle Dependency Cleanup
build.gradle
Consolidated all Lombok dependencies under a single section and removed duplicate Testcontainers dependencies for clarity and deduplication.
New OAuth Persistence Processor
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java
Added a new Spring component class implementing user persistence logic for OAuth users, handling both updates and creation with default/fallback values as needed.
OAuth User Service Refactor
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java
Refactored to delegate user persistence to the new processor, removed direct repository interaction and the internal update/create method, and added a logging statement for marketing agreement.
User Entity Update
src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java
Updated the updateOAuthUser method signature to accept and set the agreedToMarketing flag.
OAuth User Service Test
src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java
Added a new test class with a nested test to verify correct parsing of marketing consent from service terms, ensuring the absence of the marketing term results in a false value.

Sequence Diagram(s)

sequenceDiagram
    participant OAuthUserService
    participant OAuthUserPersistenceProcessor
    participant UserJpaRepository
    participant UserJpaEntity

    OAuthUserService->>OAuthUserPersistenceProcessor: process(userInfo)
    OAuthUserPersistenceProcessor->>UserJpaRepository: findByLoginId(email)
    alt User exists
        OAuthUserPersistenceProcessor->>UserJpaEntity: updateOAuthUser(...)
        OAuthUserPersistenceProcessor-->>OAuthUserService: return updated UserJpaEntity
    else User does not exist
        OAuthUserPersistenceProcessor->>UserJpaRepository: save(new UserJpaEntity)
        OAuthUserPersistenceProcessor-->>OAuthUserService: return new UserJpaEntity
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Assessment against linked issues

Objective Addressed Explanation
Dirty Checking을 위한 Transaction 분리 (Refactor to separate transaction for dirty checking) (#264)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes detected.

Possibly related PRs

Suggested labels

우선순위 중

Poem

🐇
In the warren of code, dependencies aligned,
Lombok and Testcontainers now neatly combined.
OAuth flows with a processor’s might,
Marketing consent parsed just right.
With tests to ensure all’s in place,
This hop brings clarity, order, and grace!
🌱

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.


📜 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 caa0723 and 572ddb0.

📒 Files selected for processing (2)
  • src/main/resources/db/migration/V1__init.sql (1 hunks)
  • src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/resources/db/migration/V1__init.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/mosu-264

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.
  • 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.

Support

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

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 generate unit tests to generate unit tests for 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

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @wlgns12370, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I've refactored the OAuth user handling logic to separate the transaction management for user persistence. The core change involves extracting the user creation and update logic into a dedicated OAuthUserPersistenceProcessor component. This ensures that the database operations for OAuth users are managed within their own explicit transaction boundaries, which is crucial for 'Dirty Checking' to function correctly and predictably. The OAuthUserService now orchestrates the OAuth flow by delegating persistence concerns to this new processor, leading to a cleaner separation of concerns. Additionally, I've updated the user entity to properly handle marketing agreement status and added a specific test case for parsing these terms.

Highlights

  • Transaction Separation for OAuth User Persistence: I've introduced a new component, OAuthUserPersistenceProcessor, to encapsulate the logic for persisting OAuth user data. This component now handles whether to update an existing user or create a new one, and its process method is explicitly marked with @Transactional.
  • Refactoring of OAuthUserService: The OAuthUserService has been refactored to delegate all user persistence operations to the new OAuthUserPersistenceProcessor. This removes the direct UserJpaRepository dependency and the updateOrWrite method from OAuthUserService, making it more focused on OAuth flow orchestration rather than data persistence details.
  • User Entity Update for Marketing Agreement: The UserJpaEntity's updateOAuthUser method has been enhanced to accept and persist the agreedToMarketing status, ensuring that marketing agreement preferences are correctly updated during OAuth user information synchronization.
  • New Test for Marketing Agreement Parsing: I've added a new test class, OAuthUserServiceTest, specifically to validate the parsing of marketing agreement terms from Kakao's service response. This test ensures robustness in handling various scenarios for user consent.
  • Build Dependency Cleanup: Minor adjustments were made to build.gradle to reorganize Lombok dependencies and remove redundant Testcontainers entries, contributing to a cleaner and more efficient build configuration.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully refactors the OAuth user persistence logic by moving it into a separate OAuthUserPersistenceProcessor with its own transaction boundary. This is a good improvement for separation of concerns.

My review includes feedback on improving code quality and correctness:

  • Adhering to Java naming conventions for fields.
  • Improving consistency of default values and removing redundant code in the new persistence processor.
  • Enhancing the new unit test to be more robust and correctly test the service behavior.

Overall, the changes are good, and with a few adjustments, the code quality can be further improved.

public class OAuthUserService extends DefaultOAuth2UserService {

private final UserJpaRepository userRepository;
private final OAuthUserPersistenceProcessor OAuthUserPersistenceProcessor;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The field name OAuthUserPersistenceProcessor does not follow Java naming conventions, which state that field names should start with a lowercase letter (camelCase). Please rename it to oAuthUserPersistenceProcessor for consistency and readability. You will also need to update its usage on line 60.

Suggested change
private final OAuthUserPersistenceProcessor OAuthUserPersistenceProcessor;
private final OAuthUserPersistenceProcessor oAuthUserPersistenceProcessor;

Comment on lines 34 to 47
final UserJpaEntity newUser = UserJpaEntity.builder()
.loginId(info.email() != null ? info.email() : "NA")
.gender(info.gender() != null ? info.gender() : Gender.PENDING)
.name(info.name() != null ? info.name() : "NA")
.birth(info.birthDay() != null ? info.birthDay()
: LocalDate.EPOCH)
.phoneNumber(info.phoneNumber() != null ? info.phoneNumber()
: "010-0000-0000")
.userRole(UserRole.ROLE_PENDING)
.provider(AuthProvider.KAKAO)
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(info.marketingAgreed())
.build();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are a few issues in this block for creating a new user:

  1. Inconsistent Default Birth Date: The default birth date here is LocalDate.EPOCH, which is different from the one used for updates (LocalDate.of(1900, 1, 1) on line 29). This inconsistency can lead to different data for users depending on whether they were created or updated on first login, and should be fixed.

  2. Redundant Null Checks: The OAuthUserInfo factory guarantees that gender, name, and birthDay are not null. The null checks for these fields are redundant and should be removed.

  3. Magic Values: Strings like "NA" and "010-0000-0000" should be extracted to named constants for better readability and maintainability.

                    final UserJpaEntity newUser = UserJpaEntity.builder()
                            .loginId(info.email() != null ? info.email() : "NA") // Consider using a constant
                            .gender(info.gender())
                            .name(info.name())
                            .birth(info.birthDay())
                            .phoneNumber(info.phoneNumber() != null ? info.phoneNumber()
                                    : "010-0000-0000") // Consider using a constant
                            .userRole(UserRole.ROLE_PENDING)
                            .provider(AuthProvider.KAKAO)
                            .agreedToTermsOfService(true)
                            .agreedToPrivacyPolicy(true)
                            .agreedToMarketing(info.marketingAgreed())
                            .build();

Comment on lines 24 to 32
.map(existingUser -> {
existingUser.updateOAuthUser(
info.gender(),
info.name(),
info.phoneNumber(),
info.birthDay() != null ? info.birthDay() : LocalDate.of(1900, 1, 1),
info.marketingAgreed());
return existingUser;
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The .map() block can be made more concise by using Optional.peek() (available since Java 9). Also, the OAuthUserInfo factory guarantees that birthDay is not null, so the null check is redundant and can be removed.

                .peek(existingUser -> existingUser.updateOAuthUser(
                        info.gender(),
                        info.name(),
                        info.phoneNumber(),
                        info.birthDay(),
                        info.marketingAgreed()))

Comment on lines +15 to +45
@Test
@DisplayName("Service Terms 응답에 마케팅 동의(terms_03)가 없는 경우 false로 파싱한다")
void 마케팅_동의_여부를_파싱한다() {
Map<String, Object> term1 = Map.of(
"tag", "terms_02",
"required", true,
"agreed", true
);
Map<String, Object> term2 = Map.of(
"tag", "terms_01",
"required", true,
"agreed", true
);

Map<String, Object> serviceTermsAttributes = new HashMap<>();
serviceTermsAttributes.put("id", 4342056184L);
serviceTermsAttributes.put("service_terms", List.of(term1, term2));

boolean agreedToMarketing = false;
if (serviceTermsAttributes.get("service_terms") instanceof List<?> termsList) {
agreedToMarketing = termsList.stream()
.filter(term -> term instanceof Map)
.map(term -> (Map<String, Object>) term)
.filter(termMap -> "terms_03".equals(termMap.get("tag")))
.findFirst()
.map(termMap -> (Boolean) termMap.get("agreed"))
.orElse(false);
}

Assertions.assertFalse(agreedToMarketing);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test method copies logic from OAuthUserService instead of testing the service's behavior directly. This makes the test brittle; if the implementation in the service changes, this test won't fail but will become irrelevant.

A better approach for a unit test is to mock dependencies of OAuthUserService, call the loadUser method, and assert the outcome. This would provide a more robust and meaningful test of the service's functionality.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (1)
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java (1)

37-47: Harden marketing-consent parsing and avoid raw casts; extract to a method

Current parsing relies on raw Map casts and assumes agreed is Boolean, risking ClassCastException. Also, the "terms_03" tag is hard-coded inline.

  • Replace block with a dedicated parser method and a named constant.
  • Use Boolean.TRUE.equals(...) for safe boolean extraction.
-        boolean agreedToMarketing = false;
-        if (serviceTermsAttributes.get("service_terms") instanceof List<?> termsList) {
-            agreedToMarketing = termsList.stream()
-                    .filter(term -> term instanceof Map)
-                    .map(term -> (Map<String, Object>) term)
-                    .filter(termMap ->
-                            "terms_03".equals(termMap.get("tag")))
-                    .findFirst()
-                    .map(termMap -> (Boolean) termMap.get("agreed"))
-                    .orElse(false);
-        }
+        boolean agreedToMarketing = parseMarketingAgreed(serviceTermsAttributes);

Add the following helper (outside this range):

private static final String KAKAO_MARKETING_TAG = "terms_03";

@SuppressWarnings("unchecked")
private boolean parseMarketingAgreed(Map<String, Object> attrs) {
    Object list = attrs.get("service_terms");
    if (!(list instanceof List<?> termsList)) return false;
    for (Object term : termsList) {
        if (!(term instanceof Map<?, ?> termMap)) continue;
        Object tag = termMap.get("tag");
        if (!KAKAO_MARKETING_TAG.equals(tag)) continue;
        return Boolean.TRUE.equals(termMap.get("agreed"));
    }
    return false;
}
🧹 Nitpick comments (8)
src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java (1)

92-104: Consider keeping update semantics consistent with creation path

Creation uses a different default for birth (LocalDate.EPOCH) than update (1900-01-01). This asymmetry can lead to confusing data states. Prefer a single constant or null when unknown.

build.gradle (1)

36-42: Avoid redundant testImplementation for Lombok

For Lombok, compileOnly/annotationProcessor (main) and testCompileOnly/testAnnotationProcessor (test) are sufficient. testImplementation 'org.projectlombok:lombok' is unnecessary and can be removed.

     // Lombok
     compileOnly 'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'
     testCompileOnly 'org.projectlombok:lombok'
-    testImplementation 'org.projectlombok:lombok'
     testAnnotationProcessor 'org.projectlombok:lombok'
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java (3)

23-26: Field name violates Java naming conventions; rename to lowerCamelCase

Use lowerCamelCase for fields to avoid confusion with the type name.

-    private final OAuthUserPersistenceProcessor OAuthUserPersistenceProcessor;
+    private final OAuthUserPersistenceProcessor oauthUserPersistenceProcessor;

And update usages accordingly (see Line 60).


49-49: Sanitize log message and avoid logging sensitive payloads elsewhere

  • Tweak message text for readability.
  • Ensure no PII is logged. Consider removing the full response.toString() log in getServiceTerms.
-        log.info("동의 여부{}", agreedToMarketing);
+        log.info("Kakao marketing consent: {}", agreedToMarketing);

60-61: Update field usage after renaming

After renaming the field, update the invocation.

-        final UserJpaEntity oAuthUser = OAuthUserPersistenceProcessor.process(userInfo);
+        final UserJpaEntity oAuthUser = oauthUserPersistenceProcessor.process(userInfo);
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java (3)

21-22: Verify transaction boundary matches “transaction separation for Dirty Checking” intent

@Transactional uses default propagation (REQUIRED), joining any outer transaction. If the goal is to isolate commit/flush for dirty checking independently, consider propagation = REQUIRES_NEW and document why.

Would you like this changed to @Transactional(propagation = Propagation.REQUIRES_NEW) with tests around flush/commit semantics?


40-42: Avoid hard-coded placeholder phone numbers; use null or a domain-appropriate default

Persisting a fake phone like 010-0000-0000 can leak into user-facing flows. Prefer null (if the column allows) or an explicit “unknown” state.

-                            .phoneNumber(info.phoneNumber() != null ? info.phoneNumber()
-                                    : "010-0000-0000")
+                            .phoneNumber(info.phoneNumber()) // allow null when unknown

44-47: Legal/compliance: defaulting TOS/Privacy to true may be risky

You set agreedToTermsOfService(true) and agreedToPrivacyPolicy(true) without deriving from service terms. Confirm that upstream consent actually covers these and is auditable.

If Kakao returns required consent tags (e.g., terms_01, terms_02), we can parse them similar to marketing and set booleans based on the response. I can add that parsing to the service and wire it through OAuthUserInfo.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d7fb7b and 6bd062b.

📒 Files selected for processing (5)
  • build.gradle (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java (1 hunks)
  • src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java (1 hunks)
🔇 Additional comments (1)
src/main/java/life/mosu/mosuserver/domain/user/entity/UserJpaEntity.java (1)

92-104: Method signature change looks correct; ensure call sites updated and DB defaults align

Adding agreedToMarketing and assigning it is straightforward. Verify:

  • All callers updated to pass the new flag.
  • Migration/state: existing rows have a sensible default for agreed_to_marketing (DB default or handled at read time).

Comment on lines 29 to 39
info.birthDay() != null ? info.birthDay() : LocalDate.of(1900, 1, 1),
info.marketingAgreed());
return existingUser;
})
.orElseGet(() -> {
final UserJpaEntity newUser = UserJpaEntity.builder()
.loginId(info.email() != null ? info.email() : "NA")
.gender(info.gender() != null ? info.gender() : Gender.PENDING)
.name(info.name() != null ? info.name() : "NA")
.birth(info.birthDay() != null ? info.birthDay()
: LocalDate.EPOCH)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Inconsistent default birth date between update (1900-01-01) and create (EPOCH)

Update path defaults to 1900-01-01; create path to 1970-01-01. Pick a single sentinel or allow null. Consider a constant to avoid drift.

-                            info.birthDay() != null ? info.birthDay() : LocalDate.of(1900, 1, 1),
+                            info.birthDay() != null ? info.birthDay() : DEFAULT_BIRTH,
-                            .birth(info.birthDay() != null ? info.birthDay()
-                                    : LocalDate.EPOCH)
+                            .birth(info.birthDay() != null ? info.birthDay() : DEFAULT_BIRTH)

Additionally, add to the class:

private static final LocalDate DEFAULT_BIRTH = LocalDate.of(1900, 1, 1);
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java
between lines 29 and 39, the default birth date used when updating a user is
LocalDate.of(1900, 1, 1), but when creating a new user it is LocalDate.EPOCH
(1970-01-01), causing inconsistency. Define a private static final LocalDate
constant DEFAULT_BIRTH set to LocalDate.of(1900, 1, 1) at the class level, then
replace both default birth date usages with this constant to ensure consistency
and avoid drift.

Comment on lines 35 to 36
.loginId(info.email() != null ? info.email() : "NA")
.gender(info.gender() != null ? info.gender() : Gender.PENDING)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: fallback loginId "NA" will violate the unique constraint

login_id is unique. Using "NA" for multiple users without email will cause constraint violations on the second insert. Generate a unique fallback (e.g., UUID or provider-based id).

-                            .loginId(info.email() != null ? info.email() : "NA")
+                            .loginId(info.email() != null ? info.email() : java.util.UUID.randomUUID().toString())

If a deterministic provider user-id exists in info, prefer using it (e.g., kakao:{providerUserId}) to keep traceability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.loginId(info.email() != null ? info.email() : "NA")
.gender(info.gender() != null ? info.gender() : Gender.PENDING)
// ...
.loginId(info.email() != null
? info.email()
: java.util.UUID.randomUUID().toString())
.gender(info.gender() != null
? info.gender()
: Gender.PENDING)
// ...
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserPersistenceProcessor.java
around lines 35 to 36, the fallback value "NA" for loginId violates the unique
constraint because multiple users without email will share it. Replace the
fallback with a unique identifier by checking if a deterministic provider
user-id exists in info and use a format like "provider:{providerUserId}" for
traceability; if not available, generate a UUID to ensure uniqueness for each
user.

Comment on lines +33 to +42
boolean agreedToMarketing = false;
if (serviceTermsAttributes.get("service_terms") instanceof List<?> termsList) {
agreedToMarketing = termsList.stream()
.filter(term -> term instanceof Map)
.map(term -> (Map<String, Object>) term)
.filter(termMap -> "terms_03".equals(termMap.get("tag")))
.findFirst()
.map(termMap -> (Boolean) termMap.get("agreed"))
.orElse(false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid duplicating production parsing logic in tests; extract and test a method instead

This test re-implements the parsing logic instead of exercising production code. Extract the parsing into a method (e.g., parseMarketingAgreed) in the service and test that method with positive/negative cases.

I can generate parameterized tests covering presence/absence of terms_03, non-boolean agreed, and empty lists once the helper method is introduced.

🤖 Prompt for AI Agents
In
src/test/java/life/mosu/mosuserver/application/oauth/OAuthUserServiceTest.java
around lines 33 to 42, the test duplicates the parsing logic for extracting the
marketing agreement from service terms instead of using the production code. To
fix this, move the parsing logic into a dedicated method like
parseMarketingAgreed in the OAuthUserService class, then update the test to call
this method directly. After that, create parameterized tests for this method
covering cases such as presence or absence of "terms_03", non-boolean "agreed"
values, and empty lists to properly validate the parsing behavior.

@wlgns12370 wlgns12370 merged commit b680c70 into develop Aug 9, 2025
3 checks passed
@wlgns12370 wlgns12370 deleted the refactor/mosu-264 branch August 9, 2025 16:38
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.

[🛠 리팩토링] Dirty Checking을 위한 Transaction 분리

1 participant