retold-data-service 2.0.18 → 2.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  {
2
- "timestamp": "2026-03-08T20:33:17.745Z",
3
- "duration_ms": 54218,
2
+ "timestamp": "2026-03-09T04:55:24.920Z",
3
+ "duration_ms": 22241,
4
4
  "summary": {
5
- "total": 35,
6
- "passed": 32,
5
+ "total": 26,
6
+ "passed": 26,
7
7
  "failed": 0,
8
- "skipped": 3
8
+ "skipped": 0
9
9
  },
10
10
  "storage_engines": {},
11
11
  "suites": [
@@ -20,7 +20,7 @@
20
20
  {
21
21
  "name": "Should show initial connection status",
22
22
  "status": "pass",
23
- "duration_ms": 1
23
+ "duration_ms": 2
24
24
  },
25
25
  {
26
26
  "name": "Should connect SQLite via configure",
@@ -30,10 +30,10 @@
30
30
  {
31
31
  "name": "Should show connected after configure",
32
32
  "status": "pass",
33
- "duration_ms": 0
33
+ "duration_ms": 1
34
34
  }
35
35
  ],
36
- "duration_ms": 3
36
+ "duration_ms": 5
37
37
  },
38
38
  {
39
39
  "name": "Session Configuration",
@@ -46,7 +46,7 @@
46
46
  {
47
47
  "name": "Should show session as configured",
48
48
  "status": "pass",
49
- "duration_ms": 9
49
+ "duration_ms": 10
50
50
  },
51
51
  {
52
52
  "name": "Should reject session configure without ServerURL",
@@ -54,7 +54,7 @@
54
54
  "duration_ms": 1
55
55
  }
56
56
  ],
57
- "duration_ms": 11
57
+ "duration_ms": 12
58
58
  },
59
59
  {
60
60
  "name": "Schema Fetch",
@@ -89,7 +89,7 @@
89
89
  {
90
90
  "name": "Should start initial sync with record cap",
91
91
  "status": "pass",
92
- "duration_ms": 1007
92
+ "duration_ms": 1005
93
93
  },
94
94
  {
95
95
  "name": "All tables should have completed",
@@ -99,7 +99,7 @@
99
99
  {
100
100
  "name": "Should have a valid sync report",
101
101
  "status": "pass",
102
- "duration_ms": 3
102
+ "duration_ms": 2
103
103
  },
104
104
  {
105
105
  "name": "Report tables should have timing data",
@@ -107,7 +107,7 @@
107
107
  "duration_ms": 1
108
108
  }
109
109
  ],
110
- "duration_ms": 1012
110
+ "duration_ms": 1009
111
111
  },
112
112
  {
113
113
  "name": "Data Integrity",
@@ -115,12 +115,12 @@
115
115
  {
116
116
  "name": "Should fetch harness reference data",
117
117
  "status": "pass",
118
- "duration_ms": 9
118
+ "duration_ms": 7
119
119
  },
120
120
  {
121
121
  "name": "Local book count should match sync (capped)",
122
122
  "status": "pass",
123
- "duration_ms": 2
123
+ "duration_ms": 1
124
124
  },
125
125
  {
126
126
  "name": "Local Book 1 should match harness data",
@@ -133,7 +133,7 @@
133
133
  "duration_ms": 0
134
134
  }
135
135
  ],
136
- "duration_ms": 12
136
+ "duration_ms": 9
137
137
  },
138
138
  {
139
139
  "name": "Pre-Count and Live Status",
@@ -141,10 +141,10 @@
141
141
  {
142
142
  "name": "Should run a fresh pipeline and capture pre-count data",
143
143
  "status": "pass",
144
- "duration_ms": 7885
144
+ "duration_ms": 8863
145
145
  }
146
146
  ],
147
- "duration_ms": 7885
147
+ "duration_ms": 8863
148
148
  },
149
149
  {
150
150
  "name": "Ongoing Sync",
@@ -152,7 +152,7 @@
152
152
  {
153
153
  "name": "Should run ongoing sync after initial",
154
154
  "status": "pass",
155
- "duration_ms": 1019
155
+ "duration_ms": 1015
156
156
  },
157
157
  {
158
158
  "name": "Ongoing sync report should show success",
@@ -160,7 +160,7 @@
160
160
  "duration_ms": 1
161
161
  }
162
162
  ],
163
- "duration_ms": 1020
163
+ "duration_ms": 1016
164
164
  },
165
165
  {
166
166
  "name": "Stop Sync",
@@ -168,10 +168,10 @@
168
168
  {
169
169
  "name": "Should be able to stop a sync in progress",
170
170
  "status": "pass",
171
- "duration_ms": 4063
171
+ "duration_ms": 4020
172
172
  }
173
173
  ],
174
- "duration_ms": 4063
174
+ "duration_ms": 4020
175
175
  },
176
176
  {
177
177
  "name": "Reset",
@@ -179,7 +179,7 @@
179
179
  {
180
180
  "name": "Should reset the database",
181
181
  "status": "pass",
182
- "duration_ms": 2
182
+ "duration_ms": 1
183
183
  },
184
184
  {
185
185
  "name": "Connection should still work after reset",
@@ -187,125 +187,44 @@
187
187
  "duration_ms": 1
188
188
  }
189
189
  ],
190
- "duration_ms": 3
190
+ "duration_ms": 2
191
191
  },
192
192
  {
193
193
  "name": "Storage Engine: MySQL",
194
194
  "tests": [
195
195
  {
196
196
  "name": "Should sync via MySQL",
197
- "status": "skip",
198
- "duration_ms": 0
197
+ "status": "pass",
198
+ "duration_ms": 1033
199
199
  }
200
200
  ],
201
- "duration_ms": 0
201
+ "duration_ms": 1033
202
202
  },
203
203
  {
204
204
  "name": "Storage Engine: PostgreSQL",
205
205
  "tests": [
206
206
  {
207
207
  "name": "Should sync via PostgreSQL",
208
- "status": "skip",
209
- "duration_ms": 0
208
+ "status": "pass",
209
+ "duration_ms": 1035
210
210
  }
211
211
  ],
212
- "duration_ms": 0
212
+ "duration_ms": 1035
213
213
  },
214
214
  {
215
215
  "name": "Storage Engine: MSSQL",
216
216
  "tests": [
217
217
  {
218
218
  "name": "Should sync via MSSQL",
219
- "status": "skip",
220
- "duration_ms": 0
219
+ "status": "pass",
220
+ "duration_ms": 3103
221
221
  }
222
222
  ],
223
- "duration_ms": 0
223
+ "duration_ms": 3103
224
224
  }
225
225
  ],
226
226
  "puppeteer": {
227
- "available": true,
228
- "suites": [
229
- {
230
- "name": "Data Cloner Web UI (Puppeteer)",
231
- "tests": [],
232
- "duration_ms": 0
233
- },
234
- {
235
- "name": "Web UI Load",
236
- "tests": [
237
- {
238
- "name": "Should load the data cloner page",
239
- "status": "pass",
240
- "duration_ms": 734
241
- },
242
- {
243
- "name": "Should display 7 accordion sections",
244
- "status": "pass",
245
- "duration_ms": 15
246
- },
247
- {
248
- "name": "Should show live status bar",
249
- "status": "pass",
250
- "duration_ms": 201
251
- }
252
- ],
253
- "duration_ms": 950
254
- },
255
- {
256
- "name": "Connection Section",
257
- "tests": [
258
- {
259
- "name": "Should connect to SQLite via go button",
260
- "status": "pass",
261
- "duration_ms": 2218
262
- }
263
- ],
264
- "duration_ms": 2218
265
- },
266
- {
267
- "name": "Session, Schema, and Deploy Flow",
268
- "tests": [
269
- {
270
- "name": "Should configure session via UI",
271
- "status": "pass",
272
- "duration_ms": 3019
273
- },
274
- {
275
- "name": "Should fetch schema via UI",
276
- "status": "pass",
277
- "duration_ms": 2020
278
- },
279
- {
280
- "name": "Should deploy schema via UI",
281
- "status": "pass",
282
- "duration_ms": 5074
283
- }
284
- ],
285
- "duration_ms": 10113
286
- },
287
- {
288
- "name": "Sync via Web UI",
289
- "tests": [
290
- {
291
- "name": "Should start sync and complete successfully",
292
- "status": "pass",
293
- "duration_ms": 7184
294
- }
295
- ],
296
- "duration_ms": 7184
297
- },
298
- {
299
- "name": "Live Status Display",
300
- "tests": [
301
- {
302
- "name": "Should show progress information during sync",
303
- "status": "pass",
304
- "duration_ms": 16243
305
- }
306
- ],
307
- "duration_ms": 16243
308
- }
309
- ]
227
+ "available": false,
228
+ "suites": []
310
229
  }
311
230
  }
@@ -166,24 +166,54 @@ function fPrintReport()
166
166
  console.log(` Skipped: ${_Report.summary.skipped}`);
167
167
  console.log('-'.repeat(72));
168
168
 
169
- // Storage engines
170
- console.log('\n Storage Engines:');
171
- let tmpEngines = Object.keys(_Report.storage_engines);
172
- for (let i = 0; i < tmpEngines.length; i++)
169
+ // Build storage engine status from suite results
170
+ let tmpStorageEnginePrefix = 'Storage Engine: ';
171
+ for (let i = 0; i < _Report.suites.length; i++)
173
172
  {
174
- let tmpName = tmpEngines[i];
175
- let tmpEngine = _Report.storage_engines[tmpName];
176
- if (tmpEngine.status === 'pass')
173
+ let tmpSuite = _Report.suites[i];
174
+ if (tmpSuite.name.indexOf(tmpStorageEnginePrefix) !== 0) continue;
175
+
176
+ let tmpEngineName = tmpSuite.name.substring(tmpStorageEnginePrefix.length);
177
+ let tmpPassCount = tmpSuite.tests.filter((t) => t.status === 'pass').length;
178
+ let tmpFailCount = tmpSuite.tests.filter((t) => t.status === 'fail').length;
179
+ let tmpSkipCount = tmpSuite.tests.filter((t) => t.status === 'skip').length;
180
+
181
+ if (tmpSkipCount === tmpSuite.tests.length)
177
182
  {
178
- console.log(` ${tmpName.padEnd(14)} PASS ${fFormatDuration(tmpEngine.sync_duration_ms).padStart(10)} ${tmpEngine.records_synced} records / ${tmpEngine.tables_synced} tables`);
183
+ _Report.storage_engines[tmpEngineName] = { status: 'skip', reason: 'Not configured (env vars not set)' };
179
184
  }
180
- else if (tmpEngine.status === 'fail')
185
+ else if (tmpFailCount > 0)
181
186
  {
182
- console.log(` ${tmpName.padEnd(14)} FAIL ${tmpEngine.error || 'Unknown error'}`);
187
+ let tmpFirstError = tmpSuite.tests.find((t) => t.status === 'fail');
188
+ _Report.storage_engines[tmpEngineName] = { status: 'fail', error: tmpFirstError ? tmpFirstError.error : 'Unknown' };
183
189
  }
184
190
  else
185
191
  {
186
- console.log(` ${tmpName.padEnd(14)} SKIP ${tmpEngine.reason || ''}`);
192
+ _Report.storage_engines[tmpEngineName] = { status: 'pass', sync_duration_ms: tmpSuite.duration_ms, records_synced: 0, tables_synced: 0 };
193
+ }
194
+ }
195
+
196
+ // Storage engines
197
+ let tmpEngines = Object.keys(_Report.storage_engines);
198
+ if (tmpEngines.length > 0)
199
+ {
200
+ console.log('\n Storage Engines:');
201
+ for (let i = 0; i < tmpEngines.length; i++)
202
+ {
203
+ let tmpName = tmpEngines[i];
204
+ let tmpEngine = _Report.storage_engines[tmpName];
205
+ if (tmpEngine.status === 'pass')
206
+ {
207
+ console.log(` ${tmpName.padEnd(14)} PASS ${fFormatDuration(tmpEngine.sync_duration_ms).padStart(10)} ${tmpEngine.records_synced} records / ${tmpEngine.tables_synced} tables`);
208
+ }
209
+ else if (tmpEngine.status === 'fail')
210
+ {
211
+ console.log(` ${tmpName.padEnd(14)} FAIL ${tmpEngine.error || 'Unknown error'}`);
212
+ }
213
+ else
214
+ {
215
+ console.log(` ${tmpName.padEnd(14)} SKIP ${tmpEngine.reason || ''}`);
216
+ }
187
217
  }
188
218
  }
189
219
 
@@ -194,8 +224,18 @@ function fPrintReport()
194
224
  let tmpSuite = _Report.suites[i];
195
225
  let tmpPassCount = tmpSuite.tests.filter((t) => t.status === 'pass').length;
196
226
  let tmpFailCount = tmpSuite.tests.filter((t) => t.status === 'fail').length;
227
+ let tmpSkipCount = tmpSuite.tests.filter((t) => t.status === 'skip').length;
228
+
229
+ if (tmpSkipCount === tmpSuite.tests.length && tmpSuite.tests.length > 0)
230
+ {
231
+ // All tests in this suite were skipped
232
+ console.log(` ~ ${tmpSuite.name.padEnd(35)} skipped`);
233
+ continue;
234
+ }
235
+
197
236
  let tmpStatus = tmpFailCount > 0 ? 'FAIL' : 'PASS';
198
- console.log(` ${tmpStatus === 'FAIL' ? 'X' : '+'} ${tmpSuite.name.padEnd(35)} ${tmpPassCount}/${tmpSuite.tests.length} passed ${fFormatDuration(tmpSuite.duration_ms)}`);
237
+ let tmpSkipLabel = tmpSkipCount > 0 ? ` (${tmpSkipCount} skipped)` : '';
238
+ console.log(` ${tmpStatus === 'FAIL' ? 'X' : '+'} ${tmpSuite.name.padEnd(35)} ${tmpPassCount}/${tmpPassCount + tmpFailCount} passed ${fFormatDuration(tmpSuite.duration_ms)}${tmpSkipLabel}`);
199
239
 
200
240
  // Show failed tests
201
241
  for (let j = 0; j < tmpSuite.tests.length; j++)
@@ -0,0 +1,221 @@
1
+ #!/bin/bash
2
+ # ==============================================================================
3
+ # Data Cloner Integration Tests — All Storage Engines
4
+ #
5
+ # Starts Docker containers for MySQL, PostgreSQL, and MSSQL, creates the
6
+ # required test databases, then runs the full integration test suite against
7
+ # every engine (SQLite runs without Docker).
8
+ #
9
+ # Usage:
10
+ # ./test/run-integration-tests.sh # All engines
11
+ # ./test/run-integration-tests.sh sqlite # SQLite only (no Docker)
12
+ # ./test/run-integration-tests.sh mysql # MySQL only
13
+ # ./test/run-integration-tests.sh mysql,mssql # Specific engines
14
+ #
15
+ # Prerequisites:
16
+ # - Docker (and docker compose) installed and running
17
+ # - Node.js and npm
18
+ # - npm install already run in retold-data-service
19
+ # ==============================================================================
20
+
21
+ set -euo pipefail
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
25
+ HARNESS_DIR="$(cd "$PROJECT_DIR/../retold-harness" 2>/dev/null && pwd)" || true
26
+
27
+ # ---- Parse arguments ----
28
+ ENGINES="${1:-sqlite,mysql,postgresql,mssql}"
29
+
30
+ # ---- Credentials (match retold-harness/docker-compose.yml) ----
31
+ MYSQL_ROOT_PASSWORD="1234567890"
32
+ MYSQL_TEST_DB="retold_cloner_test"
33
+
34
+ POSTGRESQL_PASSWORD="retold1234567890"
35
+ POSTGRESQL_TEST_DB="retold_cloner_test"
36
+
37
+ MSSQL_SA_PASSWORD="Retold1234567890!"
38
+ MSSQL_TEST_DB="retold_cloner_test"
39
+
40
+ # ---- Colours ----
41
+ RED='\033[0;31m'
42
+ GREEN='\033[0;32m'
43
+ YELLOW='\033[1;33m'
44
+ CYAN='\033[0;36m'
45
+ NC='\033[0m' # No colour
46
+
47
+ log() { echo -e "${CYAN}[$(date +%H:%M:%S)]${NC} $*"; }
48
+ warn() { echo -e "${YELLOW}[$(date +%H:%M:%S)] WARNING:${NC} $*"; }
49
+ err() { echo -e "${RED}[$(date +%H:%M:%S)] ERROR:${NC} $*"; }
50
+ ok() { echo -e "${GREEN}[$(date +%H:%M:%S)] OK:${NC} $*"; }
51
+
52
+ needs_docker()
53
+ {
54
+ [[ "$ENGINES" == *mysql* ]] || [[ "$ENGINES" == *postgresql* ]] || [[ "$ENGINES" == *mssql* ]]
55
+ }
56
+
57
+ # ---- Check prerequisites ----
58
+ if needs_docker; then
59
+ if ! command -v docker &>/dev/null; then
60
+ err "Docker is not installed. Install Docker to test non-SQLite engines."
61
+ err "Or run: $0 sqlite"
62
+ exit 1
63
+ fi
64
+ if ! docker info &>/dev/null; then
65
+ err "Docker daemon is not running. Start Docker first."
66
+ exit 1
67
+ fi
68
+ fi
69
+
70
+ if [ -z "$HARNESS_DIR" ] || [ ! -d "$HARNESS_DIR" ]; then
71
+ err "retold-harness not found at $PROJECT_DIR/../retold-harness"
72
+ err "Make sure retold-harness is cloned as a sibling of retold-data-service."
73
+ exit 1
74
+ fi
75
+
76
+ # ---- Start Docker containers ----
77
+ start_containers()
78
+ {
79
+ log "Starting Docker containers..."
80
+ cd "$HARNESS_DIR"
81
+
82
+ local SERVICES=""
83
+ [[ "$ENGINES" == *mysql* ]] && SERVICES="$SERVICES mysql"
84
+ [[ "$ENGINES" == *postgresql* ]] && SERVICES="$SERVICES postgresql"
85
+ [[ "$ENGINES" == *mssql* ]] && SERVICES="$SERVICES mssql"
86
+
87
+ if [ -n "$SERVICES" ]; then
88
+ docker compose up -d $SERVICES
89
+ log "Waiting for containers to become healthy..."
90
+ fi
91
+
92
+ cd "$PROJECT_DIR"
93
+ }
94
+
95
+ # Wait for a container to be healthy (or timeout after N seconds)
96
+ wait_healthy()
97
+ {
98
+ local CONTAINER="$1"
99
+ local TIMEOUT="${2:-120}"
100
+ local ELAPSED=0
101
+
102
+ while [ $ELAPSED -lt $TIMEOUT ]; do
103
+ local STATUS
104
+ STATUS="$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER" 2>/dev/null || echo "missing")"
105
+ if [ "$STATUS" = "healthy" ]; then
106
+ return 0
107
+ fi
108
+ sleep 2
109
+ ELAPSED=$((ELAPSED + 2))
110
+ done
111
+
112
+ err "$CONTAINER did not become healthy within ${TIMEOUT}s (status: $STATUS)"
113
+ return 1
114
+ }
115
+
116
+ # ---- Create test databases ----
117
+ create_mysql_db()
118
+ {
119
+ log "Waiting for MySQL to be healthy..."
120
+ wait_healthy "retold-harness-mysql" 120
121
+
122
+ log "Creating MySQL database '${MYSQL_TEST_DB}'..."
123
+ docker exec retold-harness-mysql mysql \
124
+ -uroot "-p${MYSQL_ROOT_PASSWORD}" \
125
+ -e "CREATE DATABASE IF NOT EXISTS \`${MYSQL_TEST_DB}\`;" 2>/dev/null
126
+ ok "MySQL database '${MYSQL_TEST_DB}' ready"
127
+ }
128
+
129
+ create_postgresql_db()
130
+ {
131
+ log "Waiting for PostgreSQL to be healthy..."
132
+ wait_healthy "retold-harness-postgresql" 120
133
+
134
+ log "Creating PostgreSQL database '${POSTGRESQL_TEST_DB}'..."
135
+ # createdb returns 0 even if DB exists when using IF NOT EXISTS via psql
136
+ docker exec retold-harness-postgresql psql \
137
+ -U postgres \
138
+ -tc "SELECT 1 FROM pg_database WHERE datname = '${POSTGRESQL_TEST_DB}'" \
139
+ | grep -q 1 \
140
+ || docker exec retold-harness-postgresql psql \
141
+ -U postgres \
142
+ -c "CREATE DATABASE ${POSTGRESQL_TEST_DB};"
143
+ ok "PostgreSQL database '${POSTGRESQL_TEST_DB}' ready"
144
+ }
145
+
146
+ create_mssql_db()
147
+ {
148
+ log "Waiting for MSSQL to be healthy..."
149
+ wait_healthy "retold-harness-mssql" 120
150
+
151
+ log "Creating MSSQL database '${MSSQL_TEST_DB}'..."
152
+ docker exec retold-harness-mssql /opt/mssql-tools18/bin/sqlcmd \
153
+ -S localhost -U sa -P "${MSSQL_SA_PASSWORD}" -C \
154
+ -Q "IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = '${MSSQL_TEST_DB}') CREATE DATABASE [${MSSQL_TEST_DB}];" 2>/dev/null
155
+ ok "MSSQL database '${MSSQL_TEST_DB}' ready"
156
+ }
157
+
158
+ # ---- Main ----
159
+
160
+ log "Engines requested: ${ENGINES}"
161
+ log "Project dir: ${PROJECT_DIR}"
162
+ log "Harness dir: ${HARNESS_DIR}"
163
+ echo ""
164
+
165
+ # Start Docker containers if needed
166
+ if needs_docker; then
167
+ start_containers
168
+
169
+ # Create test databases in parallel where possible
170
+ [[ "$ENGINES" == *mysql* ]] && create_mysql_db
171
+ [[ "$ENGINES" == *postgresql* ]] && create_postgresql_db
172
+ [[ "$ENGINES" == *mssql* ]] && create_mssql_db
173
+
174
+ echo ""
175
+ fi
176
+
177
+ # ---- Set environment variables ----
178
+ if [[ "$ENGINES" == *mysql* ]]; then
179
+ export MYSQL_HOST="localhost"
180
+ export MYSQL_PORT="3306"
181
+ export MYSQL_USER="root"
182
+ export MYSQL_PASSWORD="${MYSQL_ROOT_PASSWORD}"
183
+ export MYSQL_DATABASE="${MYSQL_TEST_DB}"
184
+ ok "MySQL env configured (localhost:3306, db=${MYSQL_TEST_DB})"
185
+ fi
186
+
187
+ if [[ "$ENGINES" == *postgresql* ]]; then
188
+ export POSTGRESQL_HOST="localhost"
189
+ export POSTGRESQL_PORT="5432"
190
+ export POSTGRESQL_USER="postgres"
191
+ export POSTGRESQL_PASSWORD="${POSTGRESQL_PASSWORD}"
192
+ export POSTGRESQL_DATABASE="${POSTGRESQL_TEST_DB}"
193
+ ok "PostgreSQL env configured (localhost:5432, db=${POSTGRESQL_TEST_DB})"
194
+ fi
195
+
196
+ if [[ "$ENGINES" == *mssql* ]]; then
197
+ export MSSQL_HOST="localhost"
198
+ export MSSQL_PORT="1433"
199
+ export MSSQL_USER="sa"
200
+ export MSSQL_PASSWORD="${MSSQL_SA_PASSWORD}"
201
+ export MSSQL_DATABASE="${MSSQL_TEST_DB}"
202
+ ok "MSSQL env configured (localhost:1433, db=${MSSQL_TEST_DB})"
203
+ fi
204
+
205
+ echo ""
206
+ log "Running integration tests..."
207
+ echo ""
208
+
209
+ # ---- Run tests ----
210
+ cd "$PROJECT_DIR"
211
+ node test/run-integration-tests.js --skip-puppeteer --engines="${ENGINES}"
212
+ TEST_EXIT=$?
213
+
214
+ echo ""
215
+ if [ $TEST_EXIT -eq 0 ]; then
216
+ ok "All integration tests passed!"
217
+ else
218
+ err "Some tests failed (exit code: ${TEST_EXIT})"
219
+ fi
220
+
221
+ exit $TEST_EXIT