From 6f14f774766a2567e44ead666187ddd1198052b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:04:11 +0000 Subject: [PATCH 1/6] fix: inline Python code in sandbox-verification workflow to fix YAML parsing error Lines 183-184 and 188-189 had Python code at column 1 (zero indentation) inside a `run: |` block scalar, which broke YAML parsing. GitHub showed the workflow run as failed with 0 jobs because it couldn't parse the file. Fixed by inlining the Python code on single lines using semicolons. Agent-Logs-Url: https://github.com/MaximumTrainer/OpenDataMask/sessions/4c9208a0-04e7-4727-b061-0407742be792 Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --- .github/workflows/sandbox-verification.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sandbox-verification.yml b/.github/workflows/sandbox-verification.yml index 7c18979..50be15a 100644 --- a/.github/workflows/sandbox-verification.yml +++ b/.github/workflows/sandbox-verification.yml @@ -179,15 +179,9 @@ jobs: # (the backend field is String?, not an embedded object). # sys.argv avoids shell-quoting issues with special characters. if [ -z "${params}" ]; then - BODY=$(python3 -c " -import json, sys -print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2]})) -" -- "${col}" "${gtype}") + BODY=$(python3 -c "import json, sys; print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2]}))" -- "${col}" "${gtype}") else - BODY=$(python3 -c " -import json, sys -print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2], 'generatorParams': sys.argv[3]})) -" -- "${col}" "${gtype}" "${params}") + BODY=$(python3 -c "import json, sys; print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2], 'generatorParams': sys.argv[3]}))" -- "${col}" "${gtype}" "${params}") fi curl -sf -X POST "${API_BASE}/api/workspaces/${WS_ID}/tables/${TABLE_ID}/generators" \ -H "Content-Type: application/json" \ From 02da15e959156eb4666b0b852acc1a5f8a748dba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:40:40 +0000 Subject: [PATCH 2/6] fix: remove -- from python3 -c invocations in add_generator function Python does not consume -- as an option terminator; it passes it through as sys.argv[1]. This caused the JSON body to contain {"columnName":"--","generatorType":"full_name"} instead of {"columnName":"full_name","generatorType":"FULL_NAME"}, resulting in a 400 Bad Request from the API (curl exit code 22). Agent-Logs-Url: https://github.com/MaximumTrainer/OpenDataMask/sessions/bd4340f7-88e2-4b27-b879-d21be6992abf Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --- .github/workflows/sandbox-verification.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sandbox-verification.yml b/.github/workflows/sandbox-verification.yml index 50be15a..94d51d2 100644 --- a/.github/workflows/sandbox-verification.yml +++ b/.github/workflows/sandbox-verification.yml @@ -179,9 +179,9 @@ jobs: # (the backend field is String?, not an embedded object). # sys.argv avoids shell-quoting issues with special characters. if [ -z "${params}" ]; then - BODY=$(python3 -c "import json, sys; print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2]}))" -- "${col}" "${gtype}") + BODY=$(python3 -c "import json, sys; print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2]}))" "${col}" "${gtype}") else - BODY=$(python3 -c "import json, sys; print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2], 'generatorParams': sys.argv[3]}))" -- "${col}" "${gtype}" "${params}") + BODY=$(python3 -c "import json, sys; print(json.dumps({'columnName': sys.argv[1], 'generatorType': sys.argv[2], 'generatorParams': sys.argv[3]}))" "${col}" "${gtype}" "${params}") fi curl -sf -X POST "${API_BASE}/api/workspaces/${WS_ID}/tables/${TABLE_ID}/generators" \ -H "Content-Type: application/json" \ From 2f41ec47eaf3545f33c20343e451951faa045391 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:49:09 +0000 Subject: [PATCH 3/6] fix: return java.sql.Date from BIRTH_DATE generator instead of String The BIRTH_DATE generator was returning faker.date().birthday().toString() which produces a String. When PostgreSQLConnector.writeData() passes this to stmt.setObject(), PostgreSQL rejects it with: ERROR: column "date_of_birth" is of type date but expression is of type character varying Fix: return java.sql.Date(faker.date().birthday().time) so JDBC correctly maps it to a SQL DATE type. Agent-Logs-Url: https://github.com/MaximumTrainer/OpenDataMask/sessions/0451c428-2e84-42ae-b750-404b5345b2a9 Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --- .../com/opendatamask/application/service/GeneratorService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt b/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt index aebd80b..6e70e8c 100644 --- a/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt +++ b/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt @@ -91,7 +91,7 @@ class GeneratorService( GeneratorType.BTC_ADDRESS -> faker.regexify("1[A-HJ-NP-Za-km-z1-9]{33}") GeneratorType.PASSPORT_NUMBER -> faker.regexify("[A-Z]{2}[0-9]{7}") GeneratorType.DRIVERS_LICENSE -> faker.regexify("[A-Z][0-9]{7}") - GeneratorType.BIRTH_DATE -> faker.date().birthday().toString() + GeneratorType.BIRTH_DATE -> java.sql.Date(faker.date().birthday().time) GeneratorType.GENDER -> listOf("Male", "Female", "Non-binary", "Prefer not to say").random() GeneratorType.ICD_CODE -> faker.regexify("[A-Z][0-9]{2}\\.[0-9]{1,2}") GeneratorType.MEDICAL_RECORD_NUMBER -> faker.regexify("MRN-[0-9]{8}") From e1a11973d2e66f9eed9da119329297ac13948750 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:59:06 +0000 Subject: [PATCH 4/6] fix: return java.sql.Date from DATE generator and add type assertions for all date generators The DATE generator had the same string-returning bug as BIRTH_DATE: it called .toString() on the faker date, producing a String that PostgreSQL rejected for date columns. - Changed DATE generator to return java.sql.Date (matching BIRTH_DATE) - Updated DATE test to assert java.sql.Date type instead of just non-null - Added BIRTH_DATE test asserting java.sql.Date type Agent-Logs-Url: https://github.com/MaximumTrainer/OpenDataMask/sessions/2f6ca3b8-126f-44ee-940c-2ee61c615b98 Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --- .../application/service/GeneratorService.kt | 2 +- .../application/service/GeneratorServiceTest.kt | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt b/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt index 6e70e8c..95962a2 100644 --- a/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt +++ b/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt @@ -68,7 +68,7 @@ class GeneratorService( GeneratorType.ADDRESS -> faker.address().fullAddress() GeneratorType.SSN -> faker.idNumber().ssnValid() GeneratorType.CREDIT_CARD -> faker.finance().creditCard() - GeneratorType.DATE -> faker.date().past(365 * 10, TimeUnit.DAYS).toString() + GeneratorType.DATE -> java.sql.Date(faker.date().past(365 * 10, TimeUnit.DAYS).time) GeneratorType.UUID -> UUID.randomUUID().toString() GeneratorType.CONSTANT -> params?.get("value") ?: "" GeneratorType.NULL -> null diff --git a/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt b/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt index ee32bc9..aed93f1 100644 --- a/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt +++ b/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt @@ -58,9 +58,17 @@ class GeneratorServiceTest { } @Test - fun `DATE generates a non-null date string`() { + fun `DATE generates a java sql Date`() { val result = service.generateValue(GeneratorType.DATE, "2024-01-01", null) assertNotNull(result) + assertInstanceOf(java.sql.Date::class.java, result) + } + + @Test + fun `BIRTH_DATE generates a java sql Date`() { + val result = service.generateValue(GeneratorType.BIRTH_DATE, "1990-01-15", null) + assertNotNull(result) + assertInstanceOf(java.sql.Date::class.java, result) } @Test From 5fe7febaaf559d83a4ba1bc04bab07bf3e02fce4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:36:39 +0000 Subject: [PATCH 5/6] fix: return numeric types from RANDOM_INT, SEQUENTIAL, and MONEY_AMOUNT generators RANDOM_INT returned .toString() producing a String, causing PostgreSQL to reject inserts into numeric columns (e.g. "salary" of type numeric). Same issue existed in SEQUENTIAL and MONEY_AMOUNT generators. - RANDOM_INT: return Long instead of String - SEQUENTIAL: return Long instead of String - MONEY_AMOUNT: return BigDecimal instead of String - Add assertInstanceOf tests for all three generators Agent-Logs-Url: https://github.com/MaximumTrainer/OpenDataMask/sessions/888b36ec-98e1-4f51-bff9-4eee4fc169e9 Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --- .../application/service/GeneratorService.kt | 6 ++--- .../service/GeneratorServiceTest.kt | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt b/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt index 95962a2..bf4a5fe 100644 --- a/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt +++ b/backend/src/main/kotlin/com/opendatamask/application/service/GeneratorService.kt @@ -87,7 +87,7 @@ class GeneratorService( GeneratorType.PASSWORD -> faker.internet().password(8, 20, true, true, true) GeneratorType.IBAN -> faker.finance().iban() GeneratorType.SWIFT_CODE -> faker.finance().bic() - GeneratorType.MONEY_AMOUNT -> faker.commerce().price() + GeneratorType.MONEY_AMOUNT -> java.math.BigDecimal(faker.commerce().price()) GeneratorType.BTC_ADDRESS -> faker.regexify("1[A-HJ-NP-Za-km-z1-9]{33}") GeneratorType.PASSPORT_NUMBER -> faker.regexify("[A-Z]{2}[0-9]{7}") GeneratorType.DRIVERS_LICENSE -> faker.regexify("[A-Z][0-9]{7}") @@ -136,12 +136,12 @@ class GeneratorService( val step = params?.get("step")?.toLongOrNull() ?: 1L val key = columnKey ?: "default" val counter = sequentialCounters.computeIfAbsent(key) { AtomicLong(start - step) } - counter.addAndGet(step).toString() + counter.addAndGet(step) } GeneratorType.RANDOM_INT -> { val min = params?.get("min")?.toLongOrNull() ?: 1L val max = params?.get("max")?.toLongOrNull() ?: 999999L - faker.number().numberBetween(min, max).toString() + faker.number().numberBetween(min, max) } GeneratorType.CONDITIONAL -> { val jsonParams = rawParams?.let { diff --git a/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt b/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt index aed93f1..8e31239 100644 --- a/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt +++ b/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceTest.kt @@ -178,4 +178,28 @@ class GeneratorServiceTest { val result = service.generateValue(GeneratorType.CUSTOM, "fallback", null) assertEquals("fallback", result) } + + @Test + fun `RANDOM_INT generates a Long`() { + val result = service.generateValue(GeneratorType.RANDOM_INT, null, mapOf("min" to "30000", "max" to "200000")) + assertNotNull(result) + assertInstanceOf(java.lang.Long::class.java, result) + val value = result as Long + assertTrue(value in 30000..199999) + } + + @Test + fun `SEQUENTIAL generates a Long`() { + val result = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "1"), columnKey = "test:seq") + assertNotNull(result) + assertInstanceOf(java.lang.Long::class.java, result) + assertEquals(1L, result) + } + + @Test + fun `MONEY_AMOUNT generates a BigDecimal`() { + val result = service.generateValue(GeneratorType.MONEY_AMOUNT, null, null) + assertNotNull(result) + assertInstanceOf(java.math.BigDecimal::class.java, result) + } } From 0d187b5146d57a343fc5c477e5727c18cb40686e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:47:52 +0000 Subject: [PATCH 6/6] fix: update SEQUENTIAL and RANDOM_INT tests to expect Long instead of String Agent-Logs-Url: https://github.com/MaximumTrainer/OpenDataMask/sessions/39c081aa-0cd2-4537-bcbb-b33d79d5f03f Co-authored-by: MaximumTrainer <1376575+MaximumTrainer@users.noreply.github.com> --- .../service/GeneratorServiceCompositeTest.kt | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceCompositeTest.kt b/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceCompositeTest.kt index 28fc734..baa8a09 100644 --- a/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceCompositeTest.kt +++ b/backend/src/test/kotlin/com/opendatamask/application/service/GeneratorServiceCompositeTest.kt @@ -142,55 +142,53 @@ class GeneratorServiceCompositeTest { null, mapOf("start" to "10", "step" to "1"), columnKey = "test:seq_col_start" - ) as String - assertEquals("10", result) + ) as Long + assertEquals(10L, result) } @Test fun `SEQUENTIAL increments by step`() { val key = "test:seq_col_step" - val r1 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "5"), columnKey = key) as String - val r2 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "5"), columnKey = key) as String - val r3 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "5"), columnKey = key) as String - assertEquals("1", r1) - assertEquals("6", r2) - assertEquals("11", r3) + val r1 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "5"), columnKey = key) as Long + val r2 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "5"), columnKey = key) as Long + val r3 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "5"), columnKey = key) as Long + assertEquals(1L, r1) + assertEquals(6L, r2) + assertEquals(11L, r3) } @Test fun `SEQUENTIAL uses separate counters per column key`() { - val r1 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "1"), columnKey = "table:col_a") as String - val r2 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "1"), columnKey = "table:col_b") as String - assertEquals("1", r1) - assertEquals("1", r2, "Different column keys should have independent counters") + val r1 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "1"), columnKey = "table:col_a") as Long + val r2 = service.generateValue(GeneratorType.SEQUENTIAL, null, mapOf("start" to "1", "step" to "1"), columnKey = "table:col_b") as Long + assertEquals(1L, r1) + assertEquals(1L, r2, "Different column keys should have independent counters") } // ── RANDOM_INT ──────────────────────────────────────────────────────────── @Test - fun `RANDOM_INT returns string within range`() { + fun `RANDOM_INT returns Long within range`() { val result = service.generateValue( GeneratorType.RANDOM_INT, null, mapOf("min" to "1000", "max" to "9999999") - ) as String - val num = result.toLong() - assertTrue(num >= 1000, "Result $num should be >= 1000") - assertTrue(num <= 9999999, "Result $num should be <= 9999999") + ) as Long + assertTrue(result >= 1000, "Result $result should be >= 1000") + assertTrue(result <= 9999999, "Result $result should be <= 9999999") } @Test - fun `RANDOM_INT returns a string not an integer`() { + fun `RANDOM_INT returns a Long not a String`() { val result = service.generateValue(GeneratorType.RANDOM_INT, null, mapOf("min" to "1", "max" to "100")) assertNotNull(result) - assertTrue(result is String, "RANDOM_INT should return a String, got ${result?.javaClass}") + assertTrue(result is Long, "RANDOM_INT should return a Long, got ${result?.javaClass}") } @Test fun `RANDOM_INT uses defaults when no params`() { - val result = service.generateValue(GeneratorType.RANDOM_INT, null, null) as String - val num = result.toLong() - assertTrue(num >= 1) - assertTrue(num <= 999999) + val result = service.generateValue(GeneratorType.RANDOM_INT, null, null) as Long + assertTrue(result >= 1) + assertTrue(result <= 999999) } }