test/knowledge-db-import.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("fs");
var os = require("os");
var path = require("path");
var child = require("child_process");
var Datastore = require("@seald-io/nedb");
var serviceModule = require("../lib/knowledge-db-service");
var imports = require("../lib/knowledge-db-import");
var storageModule = require("../lib/knowledge-db-storage-nedb-maintenance");
var sequence = 0;
function op() { return "import_" + Date.now() + "_" + (++sequence); }
function fixture(t, options) {
var root = fs.mkdtempSync(path.join(os.tmpdir(), "clay-import-"));
t.after(function () { fs.rmSync(root, { recursive: true, force: true }); });
return { root: root, filename: path.join(root, "knowledge", ".clay-db", "databases.db"), service: serviceModule.createKnowledgeDbService(Object.assign({ projectDir: root }, options)) };
}
function schema(extra) { return [{ id: "fld_title00", name: "Title", type: "title", required: true }].concat(extra || []); }
async function database(service, name, extra) { return (await service.create({ name: name, schema: schema(extra), operationId: op() })).database; }
function row(key, values) { return { sourceKey: String(key), sourcePayload: { key: String(key), original: values }, values: values }; }
async function request(service, db, rows, namespace) {
var revision = (await service.read({ databaseId: db.id })).database.revision;
return service.importRecords({ databaseId: db.id, expectedDatabaseRevision: revision, sourceNamespace: namespace || "synthetic", rows: rows, operationId: op() });
}
async function rejects(work, code) { await assert.rejects(async function () { return work(); }, function (cause) { return cause.code === code; }); }
async function scan(service, db, count) {
var cursor = null; var ids = new Set();
do { var page = await service.query({ databaseId: db.id, limit: 100, cursor: cursor }); assert.equal(page.totals.count, count); page.records.forEach(function (record) { assert.ok(!ids.has(record.id)); ids.add(record.id); }); cursor = page.nextCursor; } while (cursor);
assert.equal(ids.size, count);
}
function isolated(script, args) {
return JSON.parse(child.execFileSync(process.execPath, ["-e", script, path.resolve(__dirname, "../lib/knowledge-db-service.js")].concat(args), { encoding: "utf8", maxBuffer: 1024 * 1024 }));
}
test("production import covers vocabulary, finance relationships, exact scans and isolated restart/backup replay", async function (t) {
var f = fixture(t, { projectSummary: function () {} }); var service = f.service; var writes = 0; var aggregateBytes = 0; var original = Datastore.prototype.updateAsync;
Datastore.prototype.updateAsync = function (query, value, options) { if (this.filename === f.filename) { writes++; aggregateBytes = Math.max(aggregateBytes, Buffer.byteLength(JSON.stringify(value))); } return original.call(this, query, value, options); };
t.after(function () { Datastore.prototype.updateAsync = original; });
var vocabulary = await database(service, "Vocabulary", [{ id: "fld_notes00", name: "Definition", type: "text" }]);
var initialWrites = writes; var start = Date.now(); var firstRows; var firstResult; var firstRequest;
for (var offset = 0; offset < 8563; offset += 100) {
var rows = [];
for (var i = offset; i < Math.min(offset + 100, 8563); i++) rows.push(row(i, { fld_title00: "Synthetic word " + i, fld_notes00: "A synthetic definition with context.\n\tUsage example " + i + ": " + "meaning ".repeat(20) }));
var revision = (await service.read({ databaseId: vocabulary.id })).database.revision;
var args = { databaseId: vocabulary.id, expectedDatabaseRevision: revision, sourceNamespace: "vocabulary", rows: rows, operationId: op() };
var result = await service.importRecords(args);
assert.equal(result.createdCount, rows.length); assert.equal(result.databaseRevision, revision + 1);
if (!offset) { firstRows = rows; firstResult = result; firstRequest = args; }
}
assert.equal(writes - initialWrites, 172);
t.diagnostic("8563 vocabulary aggregate bytes: " + aggregateBytes);
var exactReplay = await service.importRecords(firstRequest); assert.deepEqual(exactReplay.mappings, firstResult.mappings); assert.equal(exactReplay.databaseRevision, firstResult.databaseRevision); assert.equal(writes - initialWrites, 172);
assert.equal((await service.query({ databaseId: vocabulary.id })).totals.count, 8563);
var firstId = firstResult.mappings[0].recordId;
await service.updateRecord({ databaseId: vocabulary.id, recordId: firstId, expectedRevision: 1, values: { fld_notes00: "Human correction\n\tkept" }, operationId: op() });
await service.archiveRecord({ databaseId: vocabulary.id, recordId: firstResult.mappings[1].recordId, expectedRevision: 1, operationId: op() });
var beforeReplay = writes; var replay = await request(service, vocabulary, firstRows, "vocabulary"); assert.equal(replay.createdCount, 0); assert.equal(writes, beforeReplay + 1);
assert.equal((await service.readRecord({ databaseId: vocabulary.id, recordId: firstId })).record.values.fld_notes00, "Human correction\n\tkept");
assert.equal((await service.query({ databaseId: vocabulary.id, archived: true })).totals.count, 1);
await scan(service, vocabulary, 8562);
var progress = await service.readImportProgress({ databaseId: vocabulary.id, sourceNamespace: "vocabulary", limit: 100 }); assert.equal(progress.total, 8563); assert.equal(progress.nextOffset, 100);
var mappingCount = 0; var mappingOffset = 0;
do { var mappings = await service.readImportProgress({ databaseId: vocabulary.id, sourceNamespace: "vocabulary", offset: mappingOffset }); mappingCount += mappings.mappings.length; mappingOffset = mappings.nextOffset; } while (mappingOffset !== null);
assert.equal(mappingCount, 8563);
var categories = await database(service, "Categories"); var spenders = await database(service, "Spenders");
var category = await request(service, categories, [row("food", { fld_title00: "Food" }), row("travel", { fld_title00: "Travel" })], "categories");
await request(service, spenders, [row("person", { fld_title00: "Synthetic person" })], "spenders");
var finance = await database(service, "Transactions", [{ id: "fld_amount0", name: "Amount", type: "number" }, { id: "fld_category", name: "Category", type: "reference", target: "record", targetDatabaseId: categories.id }, { id: "fld_spender", name: "Spender", type: "reference", target: "record", targetDatabaseId: spenders.id }]);
var financeRows = []; var expectedSum = 0;
for (var j = 0; j < 537; j++) { expectedSum += j; financeRows.push(row(j, { fld_title00: "Transaction " + j, fld_amount0: j, fld_category: { type: "record", sourceNamespace: "categories", sourceKey: "food" }, fld_spender: { type: "record", sourceNamespace: "spenders", sourceKey: "person" } })); }
var financeWrites = writes;
for (var n = 0; n < financeRows.length; n += 100) await request(service, finance, financeRows.slice(n, n + 100), "finance");
assert.equal(writes - financeWrites, 12); await scan(service, finance, 537);
var totals = await service.query({ databaseId: finance.id }); assert.equal(totals.totals.sums.fld_amount0, expectedSum); assert.equal(totals.records[0].references.length, 2); assert.ok(totals.records[0].references.every(function (reference) { return reference.status === "available"; }));
await service.archiveRecord({ databaseId: categories.id, recordId: category.mappings[0].recordId, expectedRevision: 1, operationId: op() });
assert.equal((await service.query({ databaseId: finance.id })).records[0].references[0].status, "archived");
var current = (await service.read({ databaseId: vocabulary.id })).database;
var ordinary = await service.createRecord({ databaseId: current.id, expectedDatabaseRevision: current.revision, values: { fld_title00: "Normal CRUD", fld_notes00: "After import" }, operationId: op() });
var updated = await service.updateRecord({ databaseId: current.id, recordId: ordinary.record.id, expectedRevision: 1, values: { fld_notes00: "Updated" }, operationId: op() }); assert.equal(updated.record.revision, 2);
var storageStatus = await service.readStorageStatus();
assert.equal(storageStatus.maintenancePending, false); assert.ok(storageStatus.compactions > 0);
assert.ok(storageStatus.journalBytes <= Math.max(storageModule.MIN_COMPACTION_BYTES, storageStatus.liveBytes * 4));
assert.ok(storageStatus.peakJournalBytes <= Math.max(storageModule.MIN_COMPACTION_BYTES, storageStatus.liveBytes * 4));
var replayFile = path.join(f.root, "replay.json"); fs.writeFileSync(replayFile, JSON.stringify({ databaseId: vocabulary.id, firstId: firstId, rows: firstRows, oldOperationId: firstRequest.operationId, count: 8563 }));
var restartScript = 'var fs = require("fs"); var assert = require("assert/strict"); var mod = require(process.argv[1]); var data = JSON.parse(fs.readFileSync(process.argv[3])); Date.now = function () { return Number(process.argv[4]); }; (async function () { var s = mod.createKnowledgeDbService({projectDir:process.argv[2]}); var db = (await s.read({databaseId:data.databaseId})).database; var result = await s.importRecords({databaseId:db.id, expectedDatabaseRevision:db.revision, operationId:"resume_"+Date.now()+"_fresh", sourceNamespace:"vocabulary", rows:data.rows}); assert.equal(result.createdCount,0); assert.equal(result.replayedCount,100); assert.equal((await s.read({databaseId:db.id})).database.revision,db.revision); var record = await s.readRecord({databaseId:db.id,recordId:data.firstId}); assert.equal(record.record.values.fld_notes00,"Human correction\\n\\tkept"); assert.equal(record.history.length,1); assert.equal((await s.query({databaseId:db.id})).totals.count,data.count); await assert.rejects(async function(){return s.importRecords({databaseId:db.id, expectedDatabaseRevision:db.revision, operationId:data.oldOperationId, sourceNamespace:"vocabulary", rows:data.rows});},function(e){return e.code==="OPERATION_EXPIRED";}); process.stdout.write(JSON.stringify({count:data.count, replayed:result.replayedCount})); })().catch(function(e){console.error(e);process.exit(1);});';
var future = String(Date.now() + serviceModule.IDEMPOTENCY_WINDOW_MS + 10000);
assert.equal(isolated(restartScript, [f.root, replayFile, future]).replayed, 100);
var backup = fs.mkdtempSync(path.join(os.tmpdir(), "clay-import-backup-")); t.after(function () { fs.rmSync(backup, { recursive: true, force: true }); });
fs.cpSync(path.join(f.root, "knowledge"), path.join(backup, "knowledge"), { recursive: true });
assert.equal(isolated(restartScript, [backup, replayFile, future]).count, 8563);
t.diagnostic(JSON.stringify({ rows: 8563, financeRows: 537, elapsedMs: Date.now() - start, maximumAggregateBytes: aggregateBytes, peakJournalBytes: storageStatus.peakJournalBytes, finalJournalBytes: storageStatus.journalBytes, afterRestartFileBytes: fs.statSync(f.filename).size, liveBytes: storageStatus.liveBytes, compactions: storageStatus.compactions, vocabularyCanonicalWrites: 86, vocabularyProjectionWrites: 86, financeCanonicalWrites: 6, financeProjectionWrites: 6 }));
});
test("import validates every row atomically, same-batch forward references, conflicts, revisions and archives", async function (t) {
var f = fixture(t); var s = f.service; var db = await database(s, "References", [{ id: "fld_related", name: "Related", type: "reference", target: "record" }]);
var rows = [row("first", { fld_title00: "First", fld_related: { type: "record", sourceNamespace: "synthetic", sourceKey: "second" } }), row("second", { fld_title00: "Second" })];
var original = await request(s, db, rows); var detail = await s.readRecord({ databaseId: db.id, recordId: original.mappings[0].recordId }); assert.equal(detail.record.values.fld_related.id, original.mappings[1].recordId); assert.equal(detail.record.references[0].status, "available");
var revision = (await s.read({ databaseId: db.id })).database.revision;
await rejects(function () { return s.importRecords({ databaseId: db.id, expectedDatabaseRevision: 1, sourceNamespace: "synthetic", rows: rows, operationId: op() }); }, "STALE");
var changed = JSON.parse(JSON.stringify(rows)); changed[0].sourcePayload.newContent = true;
await rejects(function () { return request(s, db, changed); }, "IMPORT_SOURCE_CONFLICT");
var changedValues = JSON.parse(JSON.stringify(rows)); changedValues[0].values.fld_title00 = "Changed";
await rejects(function () { return request(s, db, changedValues); }, "IMPORT_SOURCE_CONFLICT");
await rejects(function () { return request(s, db, [row("third", { fld_title00: "Third" }), row("invalid", { fld_title00: 42 })]); }, "INVALID");
await rejects(function () { return request(s, db, [row("third", { fld_title00: "Third" }), row("missing", { fld_title00: "Missing", fld_related: { type: "record", sourceNamespace: "synthetic", sourceKey: "absent" } })]); }, "FORBIDDEN_REFERENCE");
await rejects(function () { return request(s, db, [row("external", { fld_title00: "External", fld_related: { type: "record", databaseId: "db_outside123", id: "rec_outside1" } })]); }, "FORBIDDEN_REFERENCE");
assert.equal((await s.read({ databaseId: db.id })).database.revision, revision); assert.equal((await s.readImportProgress({ databaseId: db.id, sourceNamespace: "synthetic" })).total, 2); assert.equal((await s.query({ databaseId: db.id })).totals.count, 2);
var archived = await s.archiveRecord({ databaseId: db.id, recordId: original.mappings[1].recordId, expectedRevision: 1, operationId: op() });
await request(s, db, rows); assert.ok((await s.readRecord({ databaseId: db.id, recordId: archived.record.id })).record.archivedAt);
var recovered = await s.recoverRecord({ databaseId: db.id, recordId: archived.record.id, expectedRevision: 2, operationId: op() }); assert.equal(recovered.record.archivedAt, null); assert.equal((await s.readRecord({ databaseId: db.id, recordId: archived.record.id })).history.length, 2);
revision = (await s.read({ databaseId: db.id })).database.revision; await s.archive({ databaseId: db.id, expectedRevision: revision, operationId: op() });
await rejects(function () { return s.importRecords({ databaseId: db.id, expectedDatabaseRevision: revision + 1, sourceNamespace: "synthetic", rows: rows, operationId: op() }); }, "ARCHIVED");
assert.equal((await s.readImportProgress({ databaseId: db.id, sourceNamespace: "synthetic" })).total, 2);
});
test("finite batch, byte and aggregate envelopes and multiline text validation", async function (t) {
var f = fixture(t, { maxAggregateBytes: 8000 }); var s = f.service; var db = await database(s, "Limits", [{ id: "fld_notes00", name: "Text", type: "text" }]);
await rejects(function () { return request(s, db, new Array(101).fill(row("one", { fld_title00: "One" }))); }, "IMPORT_BATCH_LIMIT");
await rejects(function () { return request(s, db, []); }, "IMPORT_BATCH_LIMIT");
var huge = row("huge", { fld_title00: "Huge" }); huge.sourcePayload = "x".repeat(imports.MAX_BATCH_BYTES);
await rejects(function () { return request(s, db, [huge]); }, "IMPORT_BYTES_LIMIT");
var large = row("large", { fld_title00: "Large", fld_notes00: "x".repeat(7000) });
await assert.rejects(async function () { return request(s, db, [large]); }, function (cause) { return cause.code === "LIMIT" && /8000 byte aggregate/.test(cause.message); });
await rejects(function () { return request(s, db, [row("nul", { fld_title00: "Nul", fld_notes00: "a\u0000b" })]); }, "INVALID");
await rejects(function () { return request(s, db, [row("control", { fld_title00: "Control", fld_notes00: "a\u000bb" })]); }, "INVALID");
await rejects(function () { return request(s, db, [row("title", { fld_title00: "Title\n" })]); }, "INVALID");
var text = " \n\tNarrative\r\nEnd\t "; var result = await request(s, db, [row("text", { fld_title00: "Text", fld_notes00: text })]); assert.equal((await s.readRecord({ databaseId: db.id, recordId: result.mappings[0].recordId })).record.values.fld_notes00, text);
await rejects(function () { return request(s, db, [row("blank", { fld_title00: " " })]); }, "INVALID");
var bad = row("json", { fld_title00: "Bad" }); bad.sourcePayload = { number: Infinity }; await rejects(function () { return request(s, db, [bad]); }, "INVALID");
bad.sourcePayload = {}; bad.sourcePayload.self = bad.sourcePayload; await rejects(function () { return request(s, db, [bad]); }, "INVALID");
await rejects(function () { return s.readImportProgress({ databaseId: db.id, sourceNamespace: "synthetic", limit: 101 }); }, "INVALID");
assert.equal((await s.query({ databaseId: db.id })).totals.count, 1);
});
test("failed NeDB persistence leaves no in-memory import mappings or receipts and can retry", async function (t) {
var f = fixture(t); var s = f.service; var db = await database(s, "Failure"); var original = Datastore.prototype.updateAsync; var fail = true;
Datastore.prototype.updateAsync = function (query, value, options) {
if (this.filename !== f.filename || !fail) return original.call(this, query, value, options);
fail = false; var persistence = this.persistence; var persist = persistence.persistNewStateAsync;
persistence.persistNewStateAsync = async function () { throw new Error("injected disk failure"); };
return original.call(this, query, value, options).finally(function () { persistence.persistNewStateAsync = persist; });
};
t.after(function () { Datastore.prototype.updateAsync = original; });
var args = { databaseId: db.id, expectedDatabaseRevision: db.revision, sourceNamespace: "synthetic", rows: [row("one", { fld_title00: "One" })], operationId: op() };
await assert.rejects(s.importRecords(args), /injected disk failure/);
assert.equal((await s.read({ databaseId: db.id })).database.revision, db.revision); assert.equal((await s.readImportProgress({ databaseId: db.id, sourceNamespace: "synthetic" })).total, 0); assert.equal((await s.query({ databaseId: db.id })).totals.count, 0);
var result = await s.importRecords(args); assert.equal(result.createdCount, 1); assert.equal((await s.importRecords(args)).mappings[0].recordId, result.mappings[0].recordId);
});
test("measured 20000 row capacity retains finite bounds and live receipt budget", async function (t) {
var f = fixture(t, { projectSummary: function () {} }); var s = f.service; var db = await database(s, "Capacity"); var start = Date.now(); var writes = 0; var largest = 0; var original = Datastore.prototype.updateAsync;
Datastore.prototype.updateAsync = function (query, value, options) { if (this.filename === f.filename) { writes++; largest = Math.max(largest, Buffer.byteLength(JSON.stringify(value))); } return original.call(this, query, value, options); };
t.after(function () { Datastore.prototype.updateAsync = original; });
for (var offset = 0; offset < 20000; offset += 100) {
var rows = []; for (var i = offset; i < offset + 100; i++) rows.push(row(i, { fld_title00: "Synthetic entry " + i }));
try { await request(s, db, rows); } catch (cause) { t.diagnostic(JSON.stringify({ acceptedRows: offset, largestAggregateBytes: largest, elapsedMs: Date.now() - start, nextError: cause.message })); throw cause; }
}
assert.equal(writes, 400); var page = await s.query({ databaseId: db.id }); assert.equal(page.totals.count, 20000);
await rejects(function () { return request(s, db, [row("overflow", { fld_title00: "Overflow" })]); }, "LIMIT");
var current = (await s.read({ databaseId: db.id })).database;
await rejects(function () { return s.createRecord({ databaseId: db.id, expectedDatabaseRevision: current.revision, values: { fld_title00: "Overflow CRUD" }, operationId: op() }); }, "LIMIT");
await s.updateRecord({ databaseId: db.id, recordId: page.records[0].id, expectedRevision: 1, values: { fld_title00: "Corrected capacity entry" }, operationId: op() });
assert.equal((await s.readImportProgress({ databaseId: db.id, sourceNamespace: "synthetic" })).total, 20000);
var storageStatus = await s.readStorageStatus();
assert.equal(storageStatus.maintenancePending, false); assert.ok(storageStatus.compactions > 0);
assert.ok(storageStatus.journalBytes <= Math.max(storageModule.MIN_COMPACTION_BYTES, storageStatus.liveBytes * 4));
assert.ok(storageStatus.peakJournalBytes <= Math.max(storageModule.MIN_COMPACTION_BYTES, storageStatus.liveBytes * 4));
t.diagnostic(JSON.stringify({ rows: 20000, canonicalImportWrites: 200, projectionImportWrites: 200, aggregateBytes: largest, elapsedMs: Date.now() - start, peakJournalBytes: storageStatus.peakJournalBytes, finalJournalBytes: storageStatus.journalBytes, liveBytes: storageStatus.liveBytes, compactions: storageStatus.compactions, aggregateLimitBytes: serviceModule.MAX_AGGREGATE_BYTES }));
});
test("import survives ambiguous persisted success and keeps bounded summary projection behavior", async function (t) {
var summaries = 0; var f = fixture(t, { projectSummary: function () { summaries++; } }); var s = f.service; var db = await database(s, "Ambiguous failure"); var original = Datastore.prototype.updateAsync; var fail = true;
Datastore.prototype.updateAsync = function (query, value, options) {
if (this.filename !== f.filename || !fail) return original.call(this, query, value, options);
fail = false; var persistence = this.persistence; var persist = persistence.persistNewStateAsync;
persistence.persistNewStateAsync = async function (docs) { await persist.call(this, docs); throw new Error("injected failure after append"); };
return original.call(this, query, value, options).finally(function () { persistence.persistNewStateAsync = persist; });
};
t.after(function () { Datastore.prototype.updateAsync = original; });
var args = { databaseId: db.id, expectedDatabaseRevision: db.revision, sourceNamespace: "synthetic", rows: [row("one", { fld_title00: "One" })], operationId: op() };
await assert.rejects(s.importRecords(args), /injected failure after append/);
var retried = await s.importRecords(args); assert.equal(retried.createdCount, 1); assert.equal(retried.databaseRevision, db.revision + 1); assert.equal(retried.projectionPending, true);
assert.equal((await s.query({ databaseId: db.id })).totals.count, 1); assert.equal((await s.readImportProgress({ databaseId: db.id, sourceNamespace: "synthetic" })).total, 1);
assert.equal(summaries, 1); await s.retryProjection({ databaseId: db.id }); assert.equal(summaries, 2);
var writes = 0; Datastore.prototype.updateAsync = function (query, value, options) { if (this.filename === f.filename) writes++; return original.call(this, query, value, options); };
var batch = []; for (var i = 0; i < 100; i++) batch.push(row("batch-" + i, { fld_title00: "Batch " + i }));
var added = await request(s, db, batch); assert.equal(added.createdCount, 100); assert.equal(writes, 2); assert.equal(summaries, 3); assert.equal(added.projectionPending, false);
await request(s, db, batch); assert.equal(writes, 3); assert.equal(summaries, 3);
});
test("imports serialize revision checks, snapshot input, separate namespaces and reject operation reuse", async function (t) {
var f = fixture(t); var s = f.service; var db = await database(s, "Concurrency");
var args = { databaseId: db.id, expectedDatabaseRevision: 1, sourceNamespace: "first-source", rows: [row("key", { fld_title00: "Original" })], operationId: op() };
var pending = s.importRecords(args); args.rows[0].values.fld_title00 = "Caller changed input";
var competing = s.importRecords({ databaseId: db.id, expectedDatabaseRevision: 1, sourceNamespace: "second-source", rows: [row("key", { fld_title00: "Competing" })], operationId: op() });
var results = await Promise.allSettled([pending, competing]); assert.equal(results[0].status, "fulfilled"); assert.equal(results[1].reason.code, "STALE");
var first = results[0].value; assert.equal((await s.readRecord({ databaseId: db.id, recordId: first.mappings[0].recordId })).record.values.fld_title00, "Original");
await rejects(function () { return s.importRecords(args); }, "IDEMPOTENCY_MISMATCH");
var replayArgs = { databaseId: db.id, expectedDatabaseRevision: 2, sourceNamespace: "first-source", rows: [row("key", { fld_title00: "Original" })], operationId: op() };
var replay = await s.importRecords(replayArgs); assert.equal(replay.createdCount, 0); assert.equal(replay.databaseRevision, 2);
assert.deepEqual((await s.importRecords(replayArgs)).mappings, replay.mappings);
replayArgs.rows.push(row("extra", { fld_title00: "Extra" }));
await rejects(function () { return s.importRecords(replayArgs); }, "IDEMPOTENCY_MISMATCH");
var second = await request(s, db, [row("key", { fld_title00: "Other source" })], "second-source"); assert.notEqual(second.mappings[0].recordId, first.mappings[0].recordId);
var mixed = await request(s, db, [row("key", { fld_title00: "Original" }), row("new", { fld_title00: "New" })], "first-source"); assert.equal(mixed.createdCount, 1); assert.equal(mixed.replayedCount, 1); assert.equal(mixed.mappings[0].recordId, first.mappings[0].recordId);
await rejects(function () { return request(s, db, [row("uncommitted", { fld_title00: "Uncommitted" }), row("key", { fld_title00: "Changed source" })], "first-source"); }, "IMPORT_SOURCE_CONFLICT");
assert.equal((await s.query({ databaseId: db.id })).totals.count, 3); assert.equal((await s.readImportProgress({ databaseId: db.id, sourceNamespace: "first-source" })).total, 2);
await rejects(function () { return request(s, db, [row("duplicate", { fld_title00: "A" }), row("duplicate", { fld_title00: "A" })]); }, "INVALID");
await rejects(function () { return s.create({ name: "Name\n", operationId: op() }); }, "INVALID");
});