Skip to content

Commit b0f245f

Browse files
authored
fix minor bugs in tests, docs and examples related to the new 1.0.0 major release (#32)
* fix(test): update integration tests env vars * fix(tests): use staging cloudsync address and new auth system * fix(ci): propagate android emulator test exit code in CI * fix(ci): missing INTEGRATION_TEST_APIKEY env var * fix(ci): fail android test step when integration tests fail * fix(test): set apikey in offline error test * fix(test): validate new offline error response using JSON extraction * fix(examples): replace connection strings with new database id configurations
1 parent 9d103dd commit b0f245f

File tree

8 files changed

+119
-35
lines changed

8 files changed

+119
-35
lines changed

.github/workflows/main.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ jobs:
8181

8282
env:
8383
INTEGRATION_TEST_DATABASE_ID: ${{ secrets.INTEGRATION_TEST_DATABASE_ID }}
84+
INTEGRATION_TEST_APIKEY: ${{ secrets.INTEGRATION_TEST_APIKEY }}
85+
INTEGRATION_TEST_CLOUDSYNC_ADDRESS: ${{ secrets.INTEGRATION_TEST_CLOUDSYNC_ADDRESS }}
8486
INTEGRATION_TEST_OFFLINE_DATABASE_ID: ${{ secrets.INTEGRATION_TEST_OFFLINE_DATABASE_ID }}
8587

8688
steps:
@@ -126,6 +128,8 @@ jobs:
126128
-v ${{ github.workspace }}:/workspace \
127129
-w /workspace \
128130
-e INTEGRATION_TEST_DATABASE_ID="${{ env.INTEGRATION_TEST_DATABASE_ID }}" \
131+
-e INTEGRATION_TEST_APIKEY="${{ env.INTEGRATION_TEST_APIKEY }}" \
132+
-e INTEGRATION_TEST_CLOUDSYNC_ADDRESS="${{ env.INTEGRATION_TEST_CLOUDSYNC_ADDRESS }}" \
129133
-e INTEGRATION_TEST_OFFLINE_DATABASE_ID="${{ env.INTEGRATION_TEST_OFFLINE_DATABASE_ID }}" \
130134
alpine:latest \
131135
tail -f /dev/null
@@ -194,9 +198,12 @@ jobs:
194198
echo "::group::prepare the test script"
195199
make test PLATFORM=$PLATFORM ARCH=$ARCH || echo "It should fail. Running remaining commands in the emulator"
196200
cat > commands.sh << EOF
201+
set -e
197202
mv -f /data/local/tmp/sqlite3 /system/xbin
198203
cd /data/local/tmp
199204
export INTEGRATION_TEST_DATABASE_ID="$INTEGRATION_TEST_DATABASE_ID"
205+
export INTEGRATION_TEST_APIKEY="$INTEGRATION_TEST_APIKEY"
206+
export INTEGRATION_TEST_CLOUDSYNC_ADDRESS="$INTEGRATION_TEST_CLOUDSYNC_ADDRESS"
200207
export INTEGRATION_TEST_OFFLINE_DATABASE_ID="$INTEGRATION_TEST_OFFLINE_DATABASE_ID"
201208
$(make test PLATFORM=$PLATFORM ARCH=$ARCH -n)
202209
EOF
@@ -212,7 +219,8 @@ jobs:
212219
adb root
213220
adb remount
214221
adb push ${{ github.workspace }}/. /data/local/tmp/
215-
adb shell "sh /data/local/tmp/commands.sh"
222+
adb shell "sh /data/local/tmp/commands.sh; echo EXIT_CODE=\$?" | tee /tmp/adb_output.log
223+
grep -q "EXIT_CODE=0" /tmp/adb_output.log
216224
217225
- name: test sqlite-sync
218226
if: contains(matrix.name, 'linux') || matrix.name == 'windows' || ( matrix.name == 'macos' && matrix.arch != 'x86_64' )

Makefile

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,8 @@ $(BUILD_TEST)/%.o: %.c
218218
$(CC) $(T_CFLAGS) -c $< -o $@
219219

220220
# Run code coverage (--css-file $(CUSTOM_CSS))
221-
test: $(TARGET) $(TEST_TARGET) unittest
222-
@if [ -f .env ]; then \
223-
export $$(grep -v '^#' .env | xargs); \
224-
fi; \
225-
set -e; $(SQLITE3) ":memory:" -cmd ".bail on" ".load ./$<" "SELECT cloudsync_version();" # && \
226-
#for t in $(TEST_TARGET); do ./$$t; done
221+
test: $(TARGET) $(TEST_TARGET) unittest e2e
222+
set -e; $(SQLITE3) ":memory:" -cmd ".bail on" ".load ./$<" "SELECT cloudsync_version();"
227223
ifneq ($(COVERAGE),false)
228224
mkdir -p $(COV_DIR)
229225
lcov --capture --directory . --output-file $(COV_DIR)/coverage.info $(subst src, --include src,${COV_FILES})
@@ -234,6 +230,13 @@ endif
234230
unittest: $(TARGET) $(DIST_DIR)/unit$(EXE)
235231
@./$(DIST_DIR)/unit$(EXE)
236232

233+
# Run end-to-end integration tests
234+
e2e: $(TARGET) $(DIST_DIR)/integration$(EXE)
235+
@if [ -f .env ]; then \
236+
export $$(grep -v '^#' .env | xargs); \
237+
fi; \
238+
./$(DIST_DIR)/integration$(EXE)
239+
237240
OPENSSL_TARBALL = $(OPENSSL_DIR)/$(OPENSSL_VERSION).tar.gz
238241

239242
$(OPENSSL_TARBALL):
@@ -448,4 +451,4 @@ help:
448451
# Include PostgreSQL extension targets
449452
include docker/Makefile.postgresql
450453

451-
.PHONY: all clean test unittest extension help version xcframework aar
454+
.PHONY: all clean test unittest e2e extension help version xcframework aar

docs/postgresql/SPORT_APP_README_SUPABASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Sport Tracker app with SQLite Sync 🚵
22

3-
A Vite/React demonstration app showcasing [**SQLite Sync (Dev)**](https://github.com/sqliteai/sqlite-sync) implementation for **offline-first** data synchronization across multiple devices. This example illustrates how to integrate SQLite AI's sync capabilities into modern web applications with proper authentication via [Access Token](https://docs.sqlitecloud.io/docs/access-tokens) and [Row-Level Security (RLS)](https://docs.sqlitecloud.io/docs/rls).
3+
A Vite/React demonstration app showcasing [**SQLite Sync**](https://github.com/sqliteai/sqlite-sync) implementation for **offline-first** data synchronization across multiple devices. This example illustrates how to integrate SQLite AI's sync capabilities into modern web applications with proper authentication via [Access Token](https://docs.sqlitecloud.io/docs/access-tokens) and [Row-Level Security (RLS)](https://docs.sqlitecloud.io/docs/rls).
44

55
> This app uses the packed WASM version of SQLite with the [SQLite Sync extension enabled](https://www.npmjs.com/package/@sqliteai/sqlite-wasm).
66

examples/simple-todo-db/README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ Before using the local CLI, you need to set up your cloud database:
2020
2. Name your database (e.g., "todo_app.sqlite")
2121
3. Click **"Create"**
2222

23-
### 1.3 Get Connection Details
24-
1. Copy the **Connection String** (format: `sqlitecloud://projectid.sqlite.cloud/database.sqlite`)
23+
### 1.3 Enable OffSync
24+
1. Click the **OffSync** button next to your database, then **Enable OffSync** and confirm with the **Enable** button
25+
2. In the **Configuration** tab copy the **Database ID** (format: `db_*`)
26+
27+
### 1.4 Get Auth Details
28+
1. In your project dashboard, click **Settings**, then **API Keys**
2529
2. Copy an **API Key**
2630

27-
### 1.4 Configure Row-Level Security (Optional)
31+
### 1.5 Configure Row-Level Security (Optional)
2832
1. In your database dashboard, go to **"Security"****"Row-Level Security"**
2933
2. Enable RLS for tables you want to secure
3034
3. Create policies to control user access (e.g., users can only see their own tasks)
@@ -104,11 +108,11 @@ SELECT cloudsync_is_enabled('tasks');
104108

105109
```sql
106110
-- Configure connection to SQLite Cloud
107-
-- Replace with your managedDatabaseId from the OffSync page on the SQLiteCloud dashboard
111+
-- Replace with your managedDatabaseId from the OffSync page on the SQLiteCloud dashboard from Step 1.3
108112
SELECT cloudsync_network_init('your-managed-database-id');
109113

110114
-- Configure authentication:
111-
-- Set your API key from Step 1.3
115+
-- Set your API key from Step 1.4
112116
SELECT cloudsync_network_set_apikey('your-api-key-here');
113117
-- Or use token authentication (required for Row-Level Security)
114118
-- SELECT cloudsync_network_set_token('your_auth_token');

examples/to-do-app/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ cd MyApp
2424

2525
Rename the `.env.example` into `.env` and fill with your values.
2626

27-
> **⚠️ SECURITY WARNING**: This example puts database connection strings directly in `.env` files for demonstration purposes only. **Do not use this pattern in production.**
27+
> **⚠️ SECURITY WARNING**: This example puts database API Keys directly in `.env` files for demonstration purposes only. **Do not use this pattern in production.**
2828
>
2929
> **Why this is unsafe:**
30-
> - Connection strings contain sensitive credentials
30+
> - API Keys allow access to sensitive credentials
3131
> - Client-side apps expose all environment variables to users
3232
> - Anyone can inspect your app and extract database credentials
3333
>

examples/to-do-app/hooks/useCategories.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect } from 'react'
22
import { Platform } from 'react-native';
33
import { db } from "../db/dbConnection";
4-
import { ANDROID_MANAGED_DATABASE_ID, MANAGED_DATABASE_ID, API_TOKEN } from "@env";
4+
import { MANAGED_DATABASE_ID, API_TOKEN } from "@env";
55
import { getDylibPath } from "@op-engineering/op-sqlite";
66
import { randomUUID } from 'expo-crypto';
77
import { useSyncContext } from '../components/SyncContext';
@@ -72,8 +72,8 @@ const useCategories = () => {
7272
await db.execute('INSERT OR IGNORE INTO tags (uuid, name) VALUES (?, ?)', ['work', 'Work'])
7373
await db.execute('INSERT OR IGNORE INTO tags (uuid, name) VALUES (?, ?)', ['personal', 'Personal'])
7474

75-
if ((ANDROID_MANAGED_DATABASE_ID || MANAGED_DATABASE_ID) && API_TOKEN) {
76-
await db.execute(`SELECT cloudsync_network_init('${Platform.OS == 'android' && ANDROID_MANAGED_DATABASE_ID ? ANDROID_MANAGED_DATABASE_ID : MANAGED_DATABASE_ID}');`);
75+
if (MANAGED_DATABASE_ID && API_TOKEN) {
76+
await db.execute(`SELECT cloudsync_network_init('${MANAGED_DATABASE_ID}');`);
7777
await db.execute(`SELECT cloudsync_network_set_token('${API_TOKEN}');`)
7878
} else {
7979
throw new Error('No valid MANAGED_DATABASE_ID or API_TOKEN provided, cloudsync_network_init will not be called');

examples/to-do-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sqliteai/todoapp",
3-
"version": "1.0.6",
3+
"version": "1.0.7",
44
"description": "An Expo template for building apps with the SQLite CloudSync extension",
55
"repository": {
66
"type": "git",

test/integration.c

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,31 @@ int test_init (const char *db_path, int init) {
224224
rc = db_exec(db, "SELECT cloudsync_init('activities');"); RCHECK
225225
rc = db_exec(db, "SELECT cloudsync_init('workouts');"); RCHECK
226226

227-
// init network with JSON connection string
227+
// init network
228228
char network_init[1024];
229229
const char* test_db_id = getenv("INTEGRATION_TEST_DATABASE_ID");
230230
if (!test_db_id) {
231231
fprintf(stderr, "Error: INTEGRATION_TEST_DATABASE_ID not set.\n");
232232
exit(1);
233233
}
234-
snprintf(network_init, sizeof(network_init),
235-
"SELECT cloudsync_network_init('%s');", test_db_id);
234+
const char* custom_address = getenv("INTEGRATION_TEST_CLOUDSYNC_ADDRESS");
235+
if (custom_address) {
236+
snprintf(network_init, sizeof(network_init),
237+
"SELECT cloudsync_network_init_custom('%s', '%s');", custom_address, test_db_id);
238+
} else {
239+
snprintf(network_init, sizeof(network_init),
240+
"SELECT cloudsync_network_init('%s');", test_db_id);
241+
}
236242
rc = db_exec(db, network_init); RCHECK
237243

244+
const char* apikey = getenv("INTEGRATION_TEST_APIKEY");
245+
if (apikey) {
246+
char set_apikey[512];
247+
snprintf(set_apikey, sizeof(set_apikey),
248+
"SELECT cloudsync_network_set_apikey('%s');", apikey);
249+
rc = db_exec(db, set_apikey); RCHECK
250+
}
251+
238252
rc = db_expect_int(db, "SELECT COUNT(*) as count FROM activities;", 0); RCHECK
239253
rc = db_expect_int(db, "SELECT COUNT(*) as count FROM workouts;", 0); RCHECK
240254
char value[UUID_STR_MAXLEN];
@@ -294,17 +308,31 @@ int test_enable_disable(const char *db_path) {
294308
snprintf(sql, sizeof(sql), "INSERT INTO users (id, name) VALUES ('%s-should-sync', '%s-should-sync');", value, value);
295309
rc = db_exec(db, sql); RCHECK
296310

297-
// init network with JSON connection string
311+
// init network
298312
char network_init[1024];
299313
const char* test_db_id = getenv("INTEGRATION_TEST_DATABASE_ID");
300314
if (!test_db_id) {
301315
fprintf(stderr, "Error: INTEGRATION_TEST_DATABASE_ID not set.\n");
302316
exit(1);
303317
}
304-
snprintf(network_init, sizeof(network_init),
305-
"SELECT cloudsync_network_init('%s');", test_db_id);
318+
const char* custom_address = getenv("INTEGRATION_TEST_CLOUDSYNC_ADDRESS");
319+
if (custom_address) {
320+
snprintf(network_init, sizeof(network_init),
321+
"SELECT cloudsync_network_init_custom('%s', '%s');", custom_address, test_db_id);
322+
} else {
323+
snprintf(network_init, sizeof(network_init),
324+
"SELECT cloudsync_network_init('%s');", test_db_id);
325+
}
306326
rc = db_exec(db, network_init); RCHECK
307327

328+
const char* apikey = getenv("INTEGRATION_TEST_APIKEY");
329+
if (apikey) {
330+
char set_apikey[512];
331+
snprintf(set_apikey, sizeof(set_apikey),
332+
"SELECT cloudsync_network_set_apikey('%s');", apikey);
333+
rc = db_exec(db, set_apikey); RCHECK
334+
}
335+
308336
rc = db_exec(db, "SELECT cloudsync_network_send_changes();"); RCHECK
309337
rc = db_exec(db, "SELECT cloudsync_cleanup('users');"); RCHECK
310338
rc = db_exec(db, "SELECT cloudsync_cleanup('activities');"); RCHECK
@@ -324,6 +352,13 @@ int test_enable_disable(const char *db_path) {
324352
// init network with connection string + apikey
325353
rc = db_exec(db2, network_init); RCHECK
326354

355+
if (apikey) {
356+
char set_apikey2[512];
357+
snprintf(set_apikey2, sizeof(set_apikey2),
358+
"SELECT cloudsync_network_set_apikey('%s');", apikey);
359+
rc = db_exec(db2, set_apikey2); RCHECK
360+
}
361+
327362
rc = db_expect_gt0(db2, "SELECT cloudsync_network_sync(250,10) ->> '$.receive.rows';"); RCHECK
328363

329364
snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM users WHERE name='%s';", value);
@@ -362,10 +397,26 @@ int test_offline_error(const char *db_path) {
362397
}
363398

364399
char network_init[512];
365-
snprintf(network_init, sizeof(network_init), "SELECT cloudsync_network_init('%s');", offline_db_id);
400+
const char* custom_address = getenv("INTEGRATION_TEST_CLOUDSYNC_ADDRESS");
401+
if (custom_address) {
402+
snprintf(network_init, sizeof(network_init),
403+
"SELECT cloudsync_network_init_custom('%s', '%s');", custom_address, offline_db_id);
404+
} else {
405+
snprintf(network_init, sizeof(network_init),
406+
"SELECT cloudsync_network_init('%s');", offline_db_id);
407+
}
366408
rc = db_exec(db, network_init);
367409
RCHECK
368410

411+
const char* apikey = getenv("INTEGRATION_TEST_APIKEY");
412+
if (apikey) {
413+
char set_apikey[512];
414+
snprintf(set_apikey, sizeof(set_apikey),
415+
"SELECT cloudsync_network_set_apikey('%s');", apikey);
416+
rc = db_exec(db, set_apikey);
417+
RCHECK
418+
}
419+
369420
// Try to sync - this should fail with the expected error
370421
char *errmsg = NULL;
371422
rc = sqlite3_exec(db, "SELECT cloudsync_network_sync();", NULL, NULL, &errmsg);
@@ -376,17 +427,35 @@ int test_offline_error(const char *db_path) {
376427
goto abort_test;
377428
}
378429

379-
// Verify the error message contains the expected text
380-
const char *expected_error = "cloudsync_network_send_changes unable to upload BLOB changes to remote host";
381-
if (!errmsg || strstr(errmsg, expected_error) == NULL) {
382-
printf("Error: Expected error message containing '%s', but got '%s'\n",
383-
expected_error, errmsg ? errmsg : "NULL");
384-
if (errmsg) sqlite3_free(errmsg);
430+
// Verify the error JSON contains expected fields using SQLite JSON extraction
431+
if (!errmsg) {
432+
printf("Error: Expected an error message, but got NULL\n");
385433
rc = SQLITE_ERROR;
386434
goto abort_test;
387435
}
388436

389-
if (errmsg) sqlite3_free(errmsg);
437+
char verify_sql[1024];
438+
snprintf(verify_sql, sizeof(verify_sql),
439+
"SELECT json_extract('%s', '$.errors[0].status');", errmsg);
440+
rc = db_expect_str(db, verify_sql, "500");
441+
if (rc != SQLITE_OK) { printf("Offline error: unexpected status in: %s\n", errmsg); sqlite3_free(errmsg); goto abort_test; }
442+
443+
snprintf(verify_sql, sizeof(verify_sql),
444+
"SELECT json_extract('%s', '$.errors[0].code');", errmsg);
445+
rc = db_expect_str(db, verify_sql, "internal_server_error");
446+
if (rc != SQLITE_OK) { printf("Offline error: unexpected code in: %s\n", errmsg); sqlite3_free(errmsg); goto abort_test; }
447+
448+
snprintf(verify_sql, sizeof(verify_sql),
449+
"SELECT json_extract('%s', '$.errors[0].title');", errmsg);
450+
rc = db_expect_str(db, verify_sql, "Internal Server Error");
451+
if (rc != SQLITE_OK) { printf("Offline error: unexpected title in: %s\n", errmsg); sqlite3_free(errmsg); goto abort_test; }
452+
453+
snprintf(verify_sql, sizeof(verify_sql),
454+
"SELECT json_extract('%s', '$.errors[0].detail');", errmsg);
455+
rc = db_expect_str(db, verify_sql, "failed to resolve token data: failed to resolve db user for api key: db: connect sqlitecloud failed after 3 attempts: Your free node has been paused due to inactivity. To resume usage, please restart your node from your dashboard: https://dashboard.sqlitecloud.io");
456+
if (rc != SQLITE_OK) { printf("Offline error: unexpected detail in: %s\n", errmsg); sqlite3_free(errmsg); goto abort_test; }
457+
458+
sqlite3_free(errmsg);
390459
rc = SQLITE_OK;
391460

392461
ABORT_TEST
@@ -588,4 +657,4 @@ int main (void) {
588657

589658
printf("\n");
590659
return rc;
591-
}
660+
}

0 commit comments

Comments
 (0)