Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions source/api/src/main/kotlin/com/clerk/api/user/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ data class User(
@SerialName("public_metadata") val publicMetadata: String? = null,
/** JSON string containing private metadata to update. */
@SerialName("private_metadata") val privateMetadata: String? = null,
/** JSON string containing unsafe metadata to update. */
@SerialName("unsafe_metadata") val unsafeMetadata: String? = null,
Comment on lines +196 to +197
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Implementation looks good and follows existing patterns.

The addition of unsafeMetadata to UpdateParams correctly follows the same pattern as publicMetadata and privateMetadata. The type String? is appropriate for the update parameters, matching the existing metadata fields.

However, the AI summary incorrectly states that unsafeMetadata was added to the User data class. The field already exists at line 165, so this appears to be a documentation inconsistency rather than a code change.

As per coding guidelines, please add tests to cover the new unsafeMetadata parameter. The tests should verify:

  1. Updating a user with unsafeMetadata succeeds
  2. The field is properly serialized in API requests
  3. The updated metadata is correctly reflected in the returned User object

Would you like me to help generate test cases for this functionality?

Additionally, verify that the @AutoMap annotation properly handles the new field:


🏁 Script executed:

#!/bin/bash
# Description: Verify that unsafeMetadata is properly integrated with the UpdateParams serialization

# Search for how UpdateParams.toMap() is implemented or generated by @AutoMap
rg -n "toMap" --type=kotlin -A 5 -B 2

# Search for any existing tests of UpdateParams to understand the test pattern
rg -n "UpdateParams" --type=kotlin -g "*test*" -C 3

# Search for any existing usage of publicMetadata or privateMetadata updates to see the pattern
rg -n "publicMetadata|privateMetadata" --type=kotlin -g "**/*test*" -C 3

Length of output: 41447



---

🏁 Script executed:

```shell
#!/bin/bash
# Better search for test files related to User.kt and UpdateParams

# Search for test files by common naming patterns
echo "=== Searching for User-related test files ==="
fd -e kt -path "*test*" | grep -i user | head -20

echo -e "\n=== Searching for UpdateParams in any Kotlin files (broader search) ==="
rg -n "UpdateParams" --type=kotlin -B2 -A2 | head -50

echo -e "\n=== Checking for test directory structure ==="
find . -type d -name "test" -o -name "tests" 2>/dev/null | head -10

echo -e "\n=== Search for any test involving metadata updates ==="
rg -n "metadata" --type=kotlin -g "*test*" -C2 | head -40

Length of output: 3857



🏁 Script executed:

#!/bin/bash
# Search in test directory specifically

echo "=== List files in test directory ==="
find ./source/api/src/test -type f -name "*.kt" 2>/dev/null | head -20

echo -e "\n=== Search for User in test files ==="
find ./source/api/src/test -type f -name "*.kt" -exec grep -l "User" {} \; 2>/dev/null

echo -e "\n=== Search for update or Update in test files ==="
find ./source/api/src/test -type f -name "*.kt" -exec grep -l "update\|Update" {} \; 2>/dev/null | head -10

echo -e "\n=== Check for any metadata tests ==="
find ./source/api/src/test -type f -name "*.kt" | xargs grep -l "metadata" 2>/dev/null

Length of output: 1622



🏁 Script executed:

#!/bin/bash
# Check what User-related tests exist in ClerkTest.kt

echo "=== Inspect ClerkTest.kt for update-related tests ==="
grep -n "update\|Update" ./source/api/src/test/java/com/clerk/api/sdk/ClerkTest.kt

echo -e "\n=== Check if ClerkTest.kt tests User.update() ==="
grep -n -A5 -B5 "User\|user" ./source/api/src/test/java/com/clerk/api/sdk/ClerkTest.kt | head -50

echo -e "\n=== Verify the PR changes - check if any test files were modified ==="
# Look at the actual file structure to confirm no test changes
find . -name "*.kt" -path "*test*" -newer ./source/api/src/main/kotlin/com/clerk/api/user/User.kt 2>/dev/null | head -5 || echo "Cannot determine file modification times"

Length of output: 2795


Add tests for the new unsafeMetadata field in User.update().

The implementation correctly follows the existing pattern for publicMetadata and privateMetadata fields, with proper @AutoMap and @SerialName annotations. However, per coding guidelines, tests must be added to cover this new functionality since none exist in the codebase for User.update() operations.

Add tests to verify:

  1. Updating a user with unsafeMetadata succeeds
  2. The field is properly serialized in API requests via toMap()
  3. Updated metadata is correctly reflected in the returned User object

)

/**
Expand Down
149 changes: 149 additions & 0 deletions source/api/src/test/java/com/clerk/api/user/UserTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.clerk.api.user

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class UserTest {

@Test
fun `UpdateParams should include unsafeMetadata when provided`() {
// Given
val unsafeMetadataJson = """{"birthday":"11-30-1969"}"""
val params =
User.UpdateParams(firstName = "John", lastName = "Doe", unsafeMetadata = unsafeMetadataJson)

// When
val paramsMap = params.toMap()

// Then
assertNotNull("Map should not be null", paramsMap)
assertEquals("Should include firstName", "John", paramsMap["first_name"])
assertEquals("Should include lastName", "Doe", paramsMap["last_name"])
assertEquals("Should include unsafe_metadata", unsafeMetadataJson, paramsMap["unsafe_metadata"])
}

@Test
fun `UpdateParams should exclude unsafeMetadata when null`() {
// Given
val params = User.UpdateParams(firstName = "Jane", lastName = "Smith", unsafeMetadata = null)

// When
val paramsMap = params.toMap()

// Then
assertNotNull("Map should not be null", paramsMap)
assertEquals("Should include firstName", "Jane", paramsMap["first_name"])
assertEquals("Should include lastName", "Smith", paramsMap["last_name"])
assertTrue(
"Should not include unsafe_metadata when null",
!paramsMap.containsKey("unsafe_metadata"),
)
}

@Test
fun `UpdateParams should include all metadata types when provided`() {
// Given
val publicMetadataJson = """{"role":"admin"}"""
val privateMetadataJson = """{"internal_id":"12345"}"""
val unsafeMetadataJson = """{"birthday":"11-30-1969"}"""

val params =
User.UpdateParams(
firstName = "Alice",
publicMetadata = publicMetadataJson,
privateMetadata = privateMetadataJson,
unsafeMetadata = unsafeMetadataJson,
)

// When
val paramsMap = params.toMap()

// Then
assertNotNull("Map should not be null", paramsMap)
assertEquals("Should include firstName", "Alice", paramsMap["first_name"])
assertEquals("Should include public_metadata", publicMetadataJson, paramsMap["public_metadata"])
assertEquals(
"Should include private_metadata",
privateMetadataJson,
paramsMap["private_metadata"],
)
assertEquals("Should include unsafe_metadata", unsafeMetadataJson, paramsMap["unsafe_metadata"])
}

@Test
fun `UpdateParams should only include unsafeMetadata when only it is provided`() {
// Given
val unsafeMetadataJson = """{"preferences":{"theme":"dark"}}"""

val params = User.UpdateParams(unsafeMetadata = unsafeMetadataJson)

// When
val paramsMap = params.toMap()

// Then
assertNotNull("Map should not be null", paramsMap)
assertEquals("Map should contain only one entry", 1, paramsMap.size)
assertEquals("Should include unsafe_metadata", unsafeMetadataJson, paramsMap["unsafe_metadata"])
}

@Test
fun `UpdateParams serialization should include unsafe_metadata field`() {
// Given
val unsafeMetadataJson = """{"custom":"data"}"""
val params = User.UpdateParams(username = "testuser", unsafeMetadata = unsafeMetadataJson)

// When
val json = Json.encodeToString(params)

// Then
assertTrue("Serialized JSON should contain unsafe_metadata", json.contains("unsafe_metadata"))
assertTrue(
"Serialized JSON should contain the metadata value",
json.contains(unsafeMetadataJson),
)
}
Comment on lines 96 to 111
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify JSON escaping in serialization test.

The assertion on lines 107-110 checks if the serialized JSON contains the unescaped unsafeMetadataJson string. However, when kotlinx.serialization encodes a String field containing JSON, it should escape the quotes. For example:

  • Input: unsafeMetadata = """{"custom":"data"}"""
  • Expected serialized: {"username":"testuser","unsafe_metadata":"{\"custom\":\"data\"}"}

The test json.contains(unsafeMetadataJson) might fail because it's searching for the unescaped string {"custom":"data"} within escaped JSON.

Please run this test to confirm it passes, or consider updating the assertion to verify proper JSON escaping:

If the test passes, the assertion is working as intended. If it fails, consider this alternative assertion:

// Verify proper JSON escaping
assertTrue(
  "Serialized JSON should contain escaped metadata value",
  json.contains("\\\"custom\\\":\\\"data\\\"") || json.contains("{\\\"custom\\\":\\\"data\\\"}")
)

🏁 Script executed:

#!/bin/bash
# Run the specific serialization test to verify it passes
./gradlew :api:testDebugUnitTest --tests "com.clerk.api.user.UserTest.UpdateParams serialization should include unsafe_metadata field"

Length of output: 189



---

🏁 Script executed:

```shell
#!/bin/bash
# Find the User.UpdateParams class definition
rg -n "class UpdateParams|data class UpdateParams" --type=kt source/api/src

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for unsafeMetadata field definition and usage
rg -n "unsafeMetadata" --type=kt source/api/src -C 3

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Check if there are custom serializers for UpdateParams
rg -n "@Serializable|KSerializer" --type=kt source/api/src -C 2 | grep -A 5 -B 5 "UpdateParams"

Length of output: 26



---

🏁 Script executed:

```shell
#!/bin/bash
# Find User.kt file in the codebase
fd -e kt User.kt source/api/src

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Search for UpdateParams class without type restriction
rg -n "UpdateParams" source/api/src -C 5 | head -100

Length of output: 8221


🏁 Script executed:

#!/bin/bash
# Search for unsafeMetadata without type restriction
rg -n "unsafeMetadata" source/api/src -C 3 | head -100

Length of output: 8692



---

🏁 Script executed:

```shell
#!/bin/bash
# View the User.kt file to find UpdateParams definition
cat source/api/src/main/kotlin/com/clerk/api/user/User.kt

Length of output: 26371



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for @AutoMap annotation and toMap method references
rg -n "toMap|AutoMap" source/api/src -C 3 | head -80

Length of output: 6826


🏁 Script executed:

#!/bin/bash
# Check for custom serializers or Json configuration
rg -n "kotlinx.serialization|Json\\.encodeToString|KSerializer" source/api/src -C 2 | head -60

Length of output: 4541



---

🌐 Web query:

```web
kotlinx.serialization String field JSON escaping behavior quote characters

💡 Result:

Short answer: kotlinx.serialization follows the JSON spec — string values are emitted in double quotes and any interior double-quote characters are escaped with a backslash (" ) when you serialize (e.g., Json.encodeToString). If you need an unquoted/raw JSON literal (not ordinary JSON string escaping), you can use JsonUnquotedLiteral (experimental) but that can produce invalid JSON if misused. [1][2][3]

Sources:

  • Json.encodeToString behavior (kotlinx.serialization docs). [1]
  • JsonUnquotedLiteral (creates unquoted/raw literal). [2]
  • Example showing library escaping double quotes and other characters when encoding strings. [3]

Fix the JSON escaping assertion in the serialization test.

The assertion at lines 107-110 checks for an unescaped JSON substring, but kotlinx.serialization escapes double quotes when serializing String fields per the JSON specification. When unsafeMetadata = """{"custom":"data"}""" is serialized, it becomes "{\"custom\":\"data\"}" in the JSON output, so the substring {"custom":"data"} will not be found.

Update the assertion to verify the escaped version:

assertTrue(
  "Serialized JSON should contain escaped metadata value",
  json.contains("\\\"custom\\\":\\\"data\\\"") || json.contains("{\\\"custom\\\":\\\"data\\\"}")
)
🤖 Prompt for AI Agents
In source/api/src/test/java/com/clerk/api/user/UserTest.kt around lines 96 to
111, the test asserts the raw JSON substring {"custom":"data"} which won't
appear because kotlinx.serialization escapes quotes in string fields; update the
assertion (lines ~107-110) to check for the escaped form of the metadata value
(e.g., look for \"custom\":\"data\" or include the surrounding escaped braces
{\"custom\":\"data\"}) so the test verifies the serialized, escaped JSON string
instead of the unescaped substring.


@Test
fun `UpdateParams with empty unsafeMetadata should be handled correctly`() {
// Given
val emptyMetadata = "{}"
val params = User.UpdateParams(unsafeMetadata = emptyMetadata)

// When
val paramsMap = params.toMap()

// Then
assertNotNull("Map should not be null", paramsMap)
assertEquals(
"Should include unsafe_metadata with empty object",
emptyMetadata,
paramsMap["unsafe_metadata"],
)
}

@Test
fun `UpdateParams with complex nested unsafeMetadata should be preserved`() {
// Given
val complexMetadata =
"""{"user":{"preferences":{"theme":"dark","language":"en"},"settings":{"notifications":true}}}"""
val params = User.UpdateParams(firstName = "Bob", unsafeMetadata = complexMetadata)

// When
val paramsMap = params.toMap()

// Then
assertNotNull("Map should not be null", paramsMap)
assertEquals(
"Should preserve complex nested metadata",
complexMetadata,
paramsMap["unsafe_metadata"],
)
}
}
Loading