|
| 1 | +import path from "path"; |
| 2 | +import { fileURLToPath } from "url"; |
| 3 | +import { equal, match } from "node:assert"; |
| 4 | +import { getAccounts, getDatabase } from "@tableland/local"; |
| 5 | +import { afterEach, before, describe, test } from "mocha"; |
| 6 | +import { restore, spy } from "sinon"; |
| 7 | +import { temporaryWrite } from "tempy"; |
| 8 | +import mockStd from "mock-stdin"; |
| 9 | +import yargs from "yargs/yargs"; |
| 10 | +import * as modImportTable from "../src/commands/import-table.js"; |
| 11 | +import type { CommandOptions as ImportTableCommandOptions } from "../src/commands/import-table.js"; |
| 12 | +import * as mod from "../src/commands/import-data.js"; |
| 13 | +import { type GlobalOptions } from "../src/cli.js"; |
| 14 | +import { logger, wait } from "../src/utils.js"; |
| 15 | +import { |
| 16 | + TEST_TIMEOUT_FACTOR, |
| 17 | + TEST_API_BASE_URL, |
| 18 | + TEST_REGISTRY_PORT, |
| 19 | + TEST_PROJECT_ID, |
| 20 | +} from "./utils.js"; |
| 21 | + |
| 22 | +const _dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 23 | +const accounts = getAccounts(`http://127.0.0.1:${TEST_REGISTRY_PORT}`); |
| 24 | +const account = accounts[10]; |
| 25 | +const db = getDatabase(account); |
| 26 | + |
| 27 | +const defaultArgs = [ |
| 28 | + "--store", |
| 29 | + path.join(_dirname, ".studioclisession.json"), |
| 30 | + "--privateKey", |
| 31 | + account.privateKey.slice(2), |
| 32 | + "--chain", |
| 33 | + "local-tableland", |
| 34 | + "--providerUrl", |
| 35 | + `http://127.0.0.1:${TEST_REGISTRY_PORT}/`, |
| 36 | + "--apiUrl", |
| 37 | + TEST_API_BASE_URL, |
| 38 | + "--projectId", |
| 39 | + TEST_PROJECT_ID, |
| 40 | +]; |
| 41 | + |
| 42 | +describe("commands/import-data", function () { |
| 43 | + this.timeout(30000 * TEST_TIMEOUT_FACTOR); |
| 44 | + |
| 45 | + let table1: string; |
| 46 | + let table2: string; |
| 47 | + const defName1 = "data_import_1"; |
| 48 | + const defName2 = "data_import_2"; |
| 49 | + const desc = "table description"; |
| 50 | + |
| 51 | + before(async function () { |
| 52 | + const batch = await db.batch([ |
| 53 | + // use no backticks vs. including them to emulate a non-Studio vs. Studio |
| 54 | + // created table's column names (ensure csv header/col type parsing works) |
| 55 | + db.prepare(`create table ${defName1} (id int, val text);`), |
| 56 | + db.prepare(`create table ${defName2} (\`id\` int, \`val\` text);`), |
| 57 | + ]); |
| 58 | + const res = await batch[0].meta.txn?.wait(); |
| 59 | + const tableNames = res?.names ?? []; |
| 60 | + table1 = tableNames[0]; |
| 61 | + table2 = tableNames[1]; |
| 62 | + |
| 63 | + await yargs(["import", "table", table1, desc, ...defaultArgs]) |
| 64 | + .command<ImportTableCommandOptions>(modImportTable) |
| 65 | + .parse(); |
| 66 | + await yargs(["import", "table", table2, desc, ...defaultArgs]) |
| 67 | + .command<ImportTableCommandOptions>(modImportTable) |
| 68 | + .parse(); |
| 69 | + |
| 70 | + await wait(1000); |
| 71 | + }); |
| 72 | + |
| 73 | + afterEach(function () { |
| 74 | + restore(); |
| 75 | + }); |
| 76 | + |
| 77 | + test("can import with all values included", async function () { |
| 78 | + const csvStr = `id,val\n1,test_value`; |
| 79 | + const csvFile = await temporaryWrite(csvStr, { extension: "csv" }); |
| 80 | + |
| 81 | + const consoleLog = spy(logger, "log"); |
| 82 | + const stdin = mockStd.stdin(); |
| 83 | + setTimeout(() => { |
| 84 | + stdin.send("y\n"); |
| 85 | + }, 1000); |
| 86 | + await yargs(["import-data", defName1, csvFile, ...defaultArgs]) |
| 87 | + .command<GlobalOptions>(mod) |
| 88 | + .parse(); |
| 89 | + |
| 90 | + const res = consoleLog.getCall(0).firstArg; |
| 91 | + const successRes = res.match(/^(.*)$/m)[1]; |
| 92 | + |
| 93 | + equal(successRes, `successfully inserted 1 row into ${defName1}`); |
| 94 | + }); |
| 95 | + |
| 96 | + test("can import with empty row values", async function () { |
| 97 | + const csvStr = `id,val\n1,\n,test_value\n`; |
| 98 | + const csvFile = await temporaryWrite(csvStr, { extension: "csv" }); |
| 99 | + |
| 100 | + const consoleLog = spy(logger, "log"); |
| 101 | + const stdin = mockStd.stdin(); |
| 102 | + setTimeout(() => { |
| 103 | + stdin.send("y\n"); |
| 104 | + }, 1000); |
| 105 | + await yargs(["import-data", defName2, csvFile, ...defaultArgs]) |
| 106 | + .command<GlobalOptions>(mod) |
| 107 | + .parse(); |
| 108 | + |
| 109 | + const res = consoleLog.getCall(0).firstArg; |
| 110 | + const successRes = res.match(/^(.*)$/m)[1]; |
| 111 | + |
| 112 | + equal(successRes, `successfully inserted 2 rows into ${defName2}`); |
| 113 | + }); |
| 114 | + |
| 115 | + test("fails with wrong headers", async function () { |
| 116 | + const csvStr = `not_id,not_val\n1,test_value\n`; |
| 117 | + const csvFile = await temporaryWrite(csvStr, { extension: "csv" }); |
| 118 | + |
| 119 | + const consoleError = spy(logger, "error"); |
| 120 | + const stdin = mockStd.stdin(); |
| 121 | + setTimeout(() => { |
| 122 | + stdin.send("y\n"); |
| 123 | + }, 1000); |
| 124 | + await yargs(["import-data", defName2, csvFile, ...defaultArgs]) |
| 125 | + .command<GlobalOptions>(mod) |
| 126 | + .parse(); |
| 127 | + |
| 128 | + const res = consoleError.getCall(0).firstArg; |
| 129 | + const regex = new RegExp(`table ${table2} has no column named not_id`); |
| 130 | + match(res.toString(), regex); |
| 131 | + }); |
| 132 | + |
| 133 | + test("fails with mismatched header and row length", async function () { |
| 134 | + let csvStr = `id\n1,test_value\n`; |
| 135 | + let csvFile = await temporaryWrite(csvStr, { extension: "csv" }); |
| 136 | + |
| 137 | + const consoleError = spy(logger, "error"); |
| 138 | + const stdin = mockStd.stdin(); |
| 139 | + setTimeout(() => { |
| 140 | + stdin.send("y\n"); |
| 141 | + }, 1000); |
| 142 | + await yargs(["import-data", defName2, csvFile, ...defaultArgs]) |
| 143 | + .command<GlobalOptions>(mod) |
| 144 | + .parse(); |
| 145 | + |
| 146 | + let res = consoleError.getCall(0).firstArg; |
| 147 | + const regex = /Invalid Record Length/; |
| 148 | + match(res.toString(), regex); |
| 149 | + |
| 150 | + csvStr = `id,val\n1\n`; |
| 151 | + csvFile = await temporaryWrite(csvStr, { extension: "csv" }); |
| 152 | + setTimeout(() => { |
| 153 | + stdin.send("y\n"); |
| 154 | + }, 1000); |
| 155 | + await yargs(["import-data", defName2, csvFile, ...defaultArgs]) |
| 156 | + .command<GlobalOptions>(mod) |
| 157 | + .parse(); |
| 158 | + |
| 159 | + res = consoleError.getCall(0).firstArg; |
| 160 | + match(res.toString(), regex); |
| 161 | + }); |
| 162 | +}); |
0 commit comments