retold-data-service 2.0.18 → 2.0.20

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.
@@ -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
@@ -1,311 +0,0 @@
1
- {
2
- "timestamp": "2026-03-08T20:33:17.745Z",
3
- "duration_ms": 54218,
4
- "summary": {
5
- "total": 35,
6
- "passed": 32,
7
- "failed": 0,
8
- "skipped": 3
9
- },
10
- "storage_engines": {},
11
- "suites": [
12
- {
13
- "name": "Data Cloner Integration",
14
- "tests": [],
15
- "duration_ms": 0
16
- },
17
- {
18
- "name": "Connection Management",
19
- "tests": [
20
- {
21
- "name": "Should show initial connection status",
22
- "status": "pass",
23
- "duration_ms": 1
24
- },
25
- {
26
- "name": "Should connect SQLite via configure",
27
- "status": "pass",
28
- "duration_ms": 2
29
- },
30
- {
31
- "name": "Should show connected after configure",
32
- "status": "pass",
33
- "duration_ms": 0
34
- }
35
- ],
36
- "duration_ms": 3
37
- },
38
- {
39
- "name": "Session Configuration",
40
- "tests": [
41
- {
42
- "name": "Should configure session with retold-harness URL",
43
- "status": "pass",
44
- "duration_ms": 1
45
- },
46
- {
47
- "name": "Should show session as configured",
48
- "status": "pass",
49
- "duration_ms": 9
50
- },
51
- {
52
- "name": "Should reject session configure without ServerURL",
53
- "status": "pass",
54
- "duration_ms": 1
55
- }
56
- ],
57
- "duration_ms": 11
58
- },
59
- {
60
- "name": "Schema Fetch",
61
- "tests": [
62
- {
63
- "name": "Should reconfigure session for fresh schema fetch",
64
- "status": "pass",
65
- "duration_ms": 1
66
- },
67
- {
68
- "name": "Should fetch schema from retold-harness",
69
- "status": "pass",
70
- "duration_ms": 2
71
- }
72
- ],
73
- "duration_ms": 3
74
- },
75
- {
76
- "name": "Schema Deploy",
77
- "tests": [
78
- {
79
- "name": "Should deploy all tables",
80
- "status": "pass",
81
- "duration_ms": 25
82
- }
83
- ],
84
- "duration_ms": 25
85
- },
86
- {
87
- "name": "Initial Sync (SQLite)",
88
- "tests": [
89
- {
90
- "name": "Should start initial sync with record cap",
91
- "status": "pass",
92
- "duration_ms": 1007
93
- },
94
- {
95
- "name": "All tables should have completed",
96
- "status": "pass",
97
- "duration_ms": 1
98
- },
99
- {
100
- "name": "Should have a valid sync report",
101
- "status": "pass",
102
- "duration_ms": 3
103
- },
104
- {
105
- "name": "Report tables should have timing data",
106
- "status": "pass",
107
- "duration_ms": 1
108
- }
109
- ],
110
- "duration_ms": 1012
111
- },
112
- {
113
- "name": "Data Integrity",
114
- "tests": [
115
- {
116
- "name": "Should fetch harness reference data",
117
- "status": "pass",
118
- "duration_ms": 9
119
- },
120
- {
121
- "name": "Local book count should match sync (capped)",
122
- "status": "pass",
123
- "duration_ms": 2
124
- },
125
- {
126
- "name": "Local Book 1 should match harness data",
127
- "status": "pass",
128
- "duration_ms": 1
129
- },
130
- {
131
- "name": "Local author count should match sync (capped)",
132
- "status": "pass",
133
- "duration_ms": 0
134
- }
135
- ],
136
- "duration_ms": 12
137
- },
138
- {
139
- "name": "Pre-Count and Live Status",
140
- "tests": [
141
- {
142
- "name": "Should run a fresh pipeline and capture pre-count data",
143
- "status": "pass",
144
- "duration_ms": 7885
145
- }
146
- ],
147
- "duration_ms": 7885
148
- },
149
- {
150
- "name": "Ongoing Sync",
151
- "tests": [
152
- {
153
- "name": "Should run ongoing sync after initial",
154
- "status": "pass",
155
- "duration_ms": 1019
156
- },
157
- {
158
- "name": "Ongoing sync report should show success",
159
- "status": "pass",
160
- "duration_ms": 1
161
- }
162
- ],
163
- "duration_ms": 1020
164
- },
165
- {
166
- "name": "Stop Sync",
167
- "tests": [
168
- {
169
- "name": "Should be able to stop a sync in progress",
170
- "status": "pass",
171
- "duration_ms": 4063
172
- }
173
- ],
174
- "duration_ms": 4063
175
- },
176
- {
177
- "name": "Reset",
178
- "tests": [
179
- {
180
- "name": "Should reset the database",
181
- "status": "pass",
182
- "duration_ms": 2
183
- },
184
- {
185
- "name": "Connection should still work after reset",
186
- "status": "pass",
187
- "duration_ms": 1
188
- }
189
- ],
190
- "duration_ms": 3
191
- },
192
- {
193
- "name": "Storage Engine: MySQL",
194
- "tests": [
195
- {
196
- "name": "Should sync via MySQL",
197
- "status": "skip",
198
- "duration_ms": 0
199
- }
200
- ],
201
- "duration_ms": 0
202
- },
203
- {
204
- "name": "Storage Engine: PostgreSQL",
205
- "tests": [
206
- {
207
- "name": "Should sync via PostgreSQL",
208
- "status": "skip",
209
- "duration_ms": 0
210
- }
211
- ],
212
- "duration_ms": 0
213
- },
214
- {
215
- "name": "Storage Engine: MSSQL",
216
- "tests": [
217
- {
218
- "name": "Should sync via MSSQL",
219
- "status": "skip",
220
- "duration_ms": 0
221
- }
222
- ],
223
- "duration_ms": 0
224
- }
225
- ],
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
- ]
310
- }
311
- }