Skip to content

Commit

Permalink
test: add e2e test cases for upsert command (#597)
Browse files Browse the repository at this point in the history
Co-authored-by: tuan-pham-cybozu <[email protected]>
  • Loading branch information
hung-cybo and tuanphamcybozu authored Dec 1, 2023
1 parent b2ae5cd commit c068270
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 89 deletions.
2 changes: 1 addition & 1 deletion features/delete.feature
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ Feature: cli-kintone delete command
And Load app token of the app "app_for_delete" with exact permissions "view,delete" as env var: "API_TOKEN"
When I run the command with args "record delete --app $APP_ID --base-url $$TEST_KINTONE_BASE_URL --api-token $API_TOKEN --yes"
Then I should get the exit code is zero
And The app "app_for_delete" should has no records
And The app "app_for_delete" should have no records
237 changes: 182 additions & 55 deletions features/import.feature

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion features/step_definitions/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ Given(
},
);

Given(
"Load the record numbers of the app {string} as variable: {string}",
function (appKey: string, destVar: string) {
const recordNumbers = this.getRecordNumbersByAppKey(appKey);
this.replacements = { [destVar]: recordNumbers, ...this.replacements };
},
);

Given(
"Load app token of the app {string} with exact permissions {string} as env var: {string}",
function (appKey: string, permission: string, destEnvVar: string) {
Expand Down Expand Up @@ -125,7 +133,8 @@ Given(
async function (appKey, table) {
const appCredential = this.getAppCredentialByAppKey(appKey);
const apiToken = this.getAPITokenByAppAndPermissions(appKey, ["add"]);
const tempFilePath = await this.generateCsvFile(table.raw());
const csvObject = this.replacePlaceholdersInDataTables(table.raw());
const tempFilePath = await this.generateCsvFile(csvObject);
const command = `record import --file-path ${tempFilePath} --app ${appCredential.appId} --base-url $$TEST_KINTONE_BASE_URL --api-token ${apiToken}`;
this.execCliKintoneSync(command);
if (this.response.status !== 0) {
Expand Down Expand Up @@ -182,3 +191,11 @@ Then(
assert.match(this.response.stdout, reg);
},
);

Then(
"The app {string} should have {int} records",
function (appKey, numberOfRecords: number) {
const recordNumbers = this.getRecordNumbersByAppKey(appKey);
assert.equal(recordNumbers.length, numberOfRecords);
},
);
2 changes: 1 addition & 1 deletion features/step_definitions/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Then } from "../utils/world";
const NO_RECORDS_WARNING =
"No records exist in the app or match the condition.";

Then("The app {string} should has no records", function (appKey) {
Then("The app {string} should have no records", function (appKey) {
const appCredential = this.getAppCredentialByAppKey(appKey);
const apiToken = this.getAPITokenByAppAndPermissions(appKey, ["view"]);
const command = `record export --app ${appCredential.appId} --base-url $$TEST_KINTONE_BASE_URL --api-token ${apiToken}`;
Expand Down
58 changes: 32 additions & 26 deletions features/step_definitions/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import type { SupportedEncoding } from "../utils/helper";
import { SUPPORTED_ENCODING } from "../utils/helper";

Given(
"The csv file {string} with content as below:",
"The CSV file {string} with content as below:",
async function (filePath: string, table) {
await this.generateCsvFile(table.raw(), { filePath });
const csvObject = this.replacePlaceholdersInDataTables(table.raw());
await this.generateCsvFile(csvObject, { filePath });
},
);

Given(
"The csv file {string} with {string} encoded content as below:",
"The CSV file {string} with {string} encoded content as below:",
async function (filePath: string, encoding: SupportedEncoding, table) {
if (!SUPPORTED_ENCODING.includes(encoding)) {
throw new Error(`The encoding ${encoding} is not supported`);
Expand All @@ -29,31 +30,36 @@ Given(
},
);

Then("The app {string} should has records as below:", function (appKey, table) {
const appCredential = this.getAppCredentialByAppKey(appKey);
const apiToken = this.getAPITokenByAppAndPermissions(appKey, ["view"]);
const fields = table.raw()[0].join(",");
let command = `record export --app ${appCredential.appId} --base-url $$TEST_KINTONE_BASE_URL --api-token ${apiToken} --fields ${fields}`;
if (appCredential.guestSpaceId && appCredential.guestSpaceId.length > 0) {
command += ` --guest-space-id ${appCredential.guestSpaceId}`;
}
this.execCliKintoneSync(command);
if (this.response.status !== 0) {
throw new Error(`Getting records failed. Error: \n${this.response.stderr}`);
}
Then(
"The app {string} should have records as below:",
function (appKey, table) {
const appCredential = this.getAppCredentialByAppKey(appKey);
const apiToken = this.getAPITokenByAppAndPermissions(appKey, ["view"]);
const fields = table.raw()[0].join(",");
let command = `record export --app ${appCredential.appId} --base-url $$TEST_KINTONE_BASE_URL --api-token ${apiToken} --fields ${fields}`;
if (appCredential.guestSpaceId && appCredential.guestSpaceId.length > 0) {
command += ` --guest-space-id ${appCredential.guestSpaceId}`;
}
this.execCliKintoneSync(command);
if (this.response.status !== 0) {
throw new Error(
`Getting records failed. Error: \n${this.response.stderr}`,
);
}

const records = table.raw();
records.shift();
records.forEach((record: string[]) => {
const values = record
.map((field: string) => (field ? `"${field}"` : ""))
.join(",");
assert.match(this.response.stdout, new RegExp(`${values}`));
});
});
const records = this.replacePlaceholdersInDataTables(table.raw());
records.shift();
records.forEach((record: string[]) => {
const values = record
.map((field: string) => (field ? `"${field}"` : ""))
.join(",");
assert.match(this.response.stdout, new RegExp(`${values}`));
});
},
);

Then(
"The app {string} with table field should has records as below:",
"The app {string} with table field should have records as below:",
function (appKey, table) {
const appCredential = this.getAppCredentialByAppKey(appKey);
const apiToken = this.getAPITokenByAppAndPermissions(appKey, ["view"]);
Expand Down Expand Up @@ -92,7 +98,7 @@ Then(
);

Then(
"The app {string} should has attachments as below:",
"The app {string} should have attachments as below:",
function (appKey, table) {
const columns: string[] = table.raw()[0];
const requiredColumns = [
Expand Down
29 changes: 29 additions & 0 deletions features/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ export const SUPPORTED_ENCODING = <const>["utf8", "sjis"];

export type SupportedEncoding = (typeof SUPPORTED_ENCODING)[number];

export type ReplacementValue = string | string[] | number | number[] | boolean;

export type Replacements = { [key: string]: ReplacementValue };

export const execCliKintoneSync = (
args: string,
options?: { env?: { [key: string]: string }; cwd?: string },
Expand Down Expand Up @@ -137,3 +141,28 @@ export const getRecordNumbers = (appId: string, apiToken: string): string[] => {

return recordNumbers.filter((recordNumber) => recordNumber.length > 0);
};

export const replacePlaceholders = (
str: string,
replacements: Replacements,
): string => {
return str.replace(
/\$([a-zA-Z0-9_]*)(?:\[(\d+)])?/g,
(match: string, placeholder: string, index?: number) => {
if (replacements[placeholder] === undefined) {
return match;
}

const replacementValue = replacements[placeholder];
if (
Array.isArray(replacementValue) &&
index &&
replacementValue[index] !== undefined
) {
return replacementValue[index].toString();
}

return replacementValue.toString();
},
);
};
25 changes: 20 additions & 5 deletions features/utils/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import type { SpawnSyncReturns } from "child_process";
import type { Credentials, AppCredential, Permission } from "./credentials";
import * as cucumber from "@cucumber/cucumber";
import { World } from "@cucumber/cucumber";
import type { SupportedEncoding } from "./helper";
import type { SupportedEncoding, Replacements } from "./helper";
import {
generateCsvFile,
execCliKintoneSync,
generateFile,
getRecordNumbers,
replacePlaceholders,
} from "./helper";
import {
getAppCredentialByAppKey,
Expand All @@ -18,6 +19,7 @@ import {

export class OurWorld extends World {
public env: { [key: string]: string } = {};
public replacements: Replacements = {};
private _workingDir?: string;
private _credentials?: Credentials;
private _response?: SpawnSyncReturns<string>;
Expand Down Expand Up @@ -62,10 +64,13 @@ export class OurWorld extends World {
}

public execCliKintoneSync(args: string) {
this.response = execCliKintoneSync(args, {
env: this.env,
cwd: this.workingDir,
});
this.response = execCliKintoneSync(
replacePlaceholders(args, this.replacements),
{
env: this.env,
cwd: this.workingDir,
},
);
}

public async generateCsvFile(
Expand Down Expand Up @@ -163,6 +168,16 @@ export class OurWorld extends World {
const apiToken = this.getAPITokenByAppAndPermissions(appKey, ["view"]);
return getRecordNumbers(credential.appId, apiToken);
}

public replacePlaceholdersInDataTables(table: string[][]): string[][] {
if (Object.keys(this.replacements).length === 0) {
return table;
}

return table.map((row) =>
row.map((cell) => replacePlaceholders(cell, this.replacements)),
);
}
}

// Helpers to avoid having to specify generics every time
Expand Down

0 comments on commit c068270

Please sign in to comment.