first commit
CI / typecheck (push) Successful in 1m8s
CI / format (push) Failing after 1m6s
CI / lint (push) Failing after 49s
CI / test (push) Failing after 1m8s

This commit is contained in:
SDI
2026-07-15 18:05:12 +09:00
commit 12e4f17b62
4633 changed files with 817125 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
import { expect, test as setup } from '@playwright/test';
import { initializeDatabaseForTest } from './utils/database-utils';
import { initializeOpenSearchForTest } from './utils/opensearch-utils';
const authFile = 'playwright/.auth/user.json';
setup('tenant create and authenticate', async ({ page }) => {
try {
await initializeDatabaseForTest();
console.log('Database initialized for test');
} catch (error) {
console.warn(
'Database initialization failed, continuing without database cleanup:',
error,
);
}
try {
await initializeOpenSearchForTest();
console.log('OpenSearch initialized for test');
} catch (error) {
console.warn(
'OpenSearch initialization failed, continuing without OpenSearch:',
error,
);
}
await page.goto('http://localhost:3000/tenant/create', {
waitUntil: 'domcontentloaded',
});
await page.waitForTimeout(1000);
await page.locator("input[name='siteName']").click();
await page.locator("input[name='siteName']").fill('TestTenant');
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);
await page.locator("input[name='email']").click();
await page.locator("input[name='email']").fill('sdi9429@naver.com');
await page.getByRole('button', { name: 'Request Code', exact: true }).click();
await page.waitForSelector('input[name="code"]', {
state: 'visible',
timeout: 60000,
});
await page.locator("input[name='code']").click();
await page.locator("input[name='code']").fill('000000');
await page.getByRole('button', { name: 'Verify Code', exact: true }).click();
await page.locator("input[name='password']").click();
await page.locator("input[name='password']").fill('Abcd1234!');
await page.locator("input[name='confirmPassword']").click();
await page.locator("input[name='confirmPassword']").fill('Abcd1234!');
await page.getByRole('button', { name: 'Next', exact: true }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Confirm', exact: true }).click();
await page.waitForTimeout(1000);
await page.goto('http://localhost:3000/auth/sign-in', {
waitUntil: 'domcontentloaded',
});
await page.waitForTimeout(1000);
await expect(page.locator('body', { hasText: 'TestTenant' })).toContainText(
'TestTenant',
);
await page.locator("input[name='email']").click();
await page.locator("input[name='email']").fill('sdi9429@naver.com');
await page.locator("input[name='password']").click();
await page.locator("input[name='password']").fill('Abcd1234!');
await page.getByRole('button', { name: 'Sign In', exact: true }).click();
await page.waitForTimeout(1000);
await page.waitForURL('http://localhost:3000/main/project/create');
await page.context().storageState({ path: authFile });
});
+24
View File
@@ -0,0 +1,24 @@
import { test as teardown } from '@playwright/test';
import { cleanupDatabaseAfterTest } from './utils/database-utils';
import { cleanupOpenSearchAfterTest } from './utils/opensearch-utils';
teardown('teardown', async () => {
try {
try {
await cleanupDatabaseAfterTest();
} catch (error) {
console.warn('Database cleanup failed:', error);
}
try {
await cleanupOpenSearchAfterTest();
} catch (error) {
console.warn('OpenSearch cleanup failed:', error);
}
console.log('Tearing down succeeds.');
} catch (e) {
console.log('Tearing down fails.', e);
}
});
+15
View File
@@ -0,0 +1,15 @@
{
"name": "e2e",
"version": "1.0.0",
"description": "",
"scripts": {
"clean": "git clean -xdf node_modules playwright-report test-results",
"test:e2e": "playwright test"
},
"devDependencies": {
"@opensearch-project/opensearch": "^3.5.1",
"@playwright/test": "^1.58.2",
"axios": "^1.13.6",
"mysql2": "^3.20.0"
}
}
+122
View File
@@ -0,0 +1,122 @@
import * as path from 'path';
import { defineConfig, devices } from '@playwright/test';
import 'dotenv/config';
export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json');
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: '.',
/* Maximum time one test can run for. */
timeout: 60 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 60 * 1000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
testMatch: 'test.list.*',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'retain-on-failure',
video: 'on',
screenshot: 'only-on-failure',
},
/* Configure projects for major browsers */
projects: [
{
name: 'global setup',
testMatch: /global\.setup\.ts/,
teardown: 'global teardown',
},
{
name: 'global teardown',
testMatch: /global\.teardown\.ts/,
},
{
name: 'logged in chromium',
use: { ...devices['Desktop Chrome'], storageState: STORAGE_STATE },
dependencies: ['global setup'],
},
// {
// name: 'logged out chromium',
// testMatch: '**/sign-in*.spec.ts',
// use: { ...devices['Desktop Chrome'] },
// },
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
webServer: [
{
command:
!process.env.CI ?
'cd ../.. && pnpm dev:api'
: 'cd ../.. && pnpm build:api && cd apps/api && pnpm start',
port: 4000,
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
env: {
JWT_SECRET: 'jwtsecretjwtsecretjwtsecret',
BASE_URL: 'http://localhost:3000',
MYSQL_PRIMARY_URL:
'mysql://userfeedback:userfeedback@localhost:13307/e2e',
MYSQL_SECONDARY_URLS:
'["mysql://userfeedback:userfeedback@localhost:13307/e2e"]',
AUTO_MIGRATION: 'true',
MASTER_API_KEY: 'MASTER_API_KEY',
NODE_ENV: 'test',
SMTP_HOST: 'localhost',
SMTP_PORT: '25',
SMTP_SENDER: 'abc@feedback.user',
OPENSEARCH_USE: 'true',
OPENSEARCH_NODE: 'http://localhost:9200',
OPENSEARCH_USERNAME: '',
OPENSEARCH_PASSWORD: '',
},
},
{
command:
!process.env.CI ?
'cd ../.. && pnpm dev:web'
: 'cd ../.. && pnpm build:web && cd apps/web && pnpm start',
port: 3000,
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
env: {
NEXT_PUBLIC_API_BASE_URL: 'http://localhost:4000',
},
},
],
});
+52
View File
@@ -0,0 +1,52 @@
import { expect, test } from '@playwright/test';
export default () => {
test.describe('create-channel suite', () => {
test('creating a channel succeeds', async ({ page }) => {
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(1000);
await page.getByRole('radio', { name: 'Settings' }).click();
await page.waitForTimeout(1000);
await page.getByText('Create Channel').click();
await page.waitForTimeout(1000);
await page.locator('input[name="name"]').click();
await page.locator('input[name="name"]').fill('TestChannel');
await page.getByRole('button', { name: 'Next' }).click();
await page.getByText('Add Field').first().click();
await page.waitForTimeout(1000);
await page.waitForSelector('input[name="key"]', { state: 'visible' });
await page.locator('input[name="key"]').click();
await page.locator('input[name="key"]').fill('message');
await page.getByRole('button', { name: 'Confirm' }).click();
await page.getByText('Add Field').first().click();
await page.waitForTimeout(1000);
await page.waitForSelector('input[name="key"]', { state: 'visible' });
await page.locator('input[name="key"]').click();
await page.locator('input[name="key"]').fill('image');
await page
.locator('select[aria-hidden="true"]')
.first()
.selectOption('images');
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Confirm' }).click();
await page.getByRole('button', { name: 'Complete' }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Start' }).click();
await page.waitForTimeout(1000);
await expect(
page.getByRole('tab', { name: 'TestChannel' }).first(),
).toContainText('TestChannel');
});
});
};
@@ -0,0 +1,59 @@
import { expect, test } from '@playwright/test';
import axios from 'axios';
export default () => {
test.describe('create-feedback-with-image suite', () => {
test.afterEach(async ({ page }) => {
await page.getByText('test text').first().click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Delete').nth(3)).toBeVisible();
await page.getByText('Delete').nth(3).click();
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).not.toContainText('test text');
});
test('creating a feedback with image succeeds', async ({ page }) => {
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
await page.getByRole('radio', { name: 'Feedback' }).click();
await page.waitForURL(/.*channelId.*/, { timeout: 10000 });
await expect(page.getByText('There is no data yet')).toBeVisible();
const url = new URL(page.url());
const pathname = url.pathname;
const segments = pathname.split('/');
const projectId = segments[3];
const params = new URLSearchParams(url.search);
const channelId = params.get('channelId');
await axios.post(
`http://localhost:4000/api/projects/${projectId}/channels/${channelId}/feedbacks`,
{
message: 'test text',
image: [
'https://lps-editor-2050.landpress.line.me/logo/logo_line_color.svg',
],
},
{ headers: { 'x-api-key': 'MASTER_API_KEY' } },
);
await page.goto(
`http://localhost:3000/main/project/${projectId}/feedback?channelId=${channelId}`,
{ waitUntil: 'domcontentloaded' },
);
await page.waitForTimeout(1000);
await page.getByText('Image').nth(1).click();
await page.waitForTimeout(1000);
await page.getByText('Cancel').nth(1).click();
await page.waitForTimeout(1000);
});
});
};
@@ -0,0 +1,73 @@
import { expect, test } from '@playwright/test';
import axios from 'axios';
import dayjs from 'dayjs';
export default () => {
test.describe('create-feedback suite', () => {
test.afterEach(async ({ page }) => {
await page.getByText('test text').first().click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Delete').nth(3)).toBeVisible();
await page.getByText('Delete').nth(3).click();
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).not.toContainText('test text');
});
test('creating a feedback succeeds', async ({ page }) => {
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
await page.getByRole('radio', { name: 'Feedback' }).click();
await page.waitForURL(/.*channelId.*/, { timeout: 10000 });
await expect(page.getByText('There is no data yet')).toBeVisible();
const url = new URL(page.url());
const pathname = url.pathname;
const segments = pathname.split('/');
const projectId = segments[3];
const params = new URLSearchParams(url.search);
const channelId = params.get('channelId');
await axios.post(
`http://localhost:4000/api/projects/${projectId}/channels/${channelId}/feedbacks`,
{
message: 'test text',
},
{ headers: { 'x-api-key': 'MASTER_API_KEY' } },
);
await page.goto(
`http://localhost:3000/main/project/${projectId}/feedback?channelId=${channelId}`,
{ waitUntil: 'domcontentloaded' },
);
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).toContainText('test text');
let dateSelector = page.getByText(
`${dayjs().subtract(364, 'day').format('YYYY-MM-DD')} ~ ${dayjs().format('YYYY-MM-DD')}`,
);
await expect(dateSelector).toBeVisible();
await dateSelector.click();
await page.getByText('Yesterday').click();
await page.getByText('Save').click();
await page.waitForTimeout(1000);
await expect(page.getByText('There is no data yet')).toBeVisible();
dateSelector = page.getByText(
`${dayjs().subtract(1, 'day').format('YYYY-MM-DD')} ~ ${dayjs().subtract(1, 'day').format('YYYY-MM-DD')}`,
);
await dateSelector.click();
await page.getByText('Today').click();
await page.getByText('Save').click();
await page.waitForTimeout(1000);
});
});
};
+83
View File
@@ -0,0 +1,83 @@
import { expect, test } from '@playwright/test';
import axios from 'axios';
export default () => {
test.describe('create-issue suite', () => {
test.afterEach(async ({ page }) => {
await page
.getByRole('button', { name: 'Delete' })
.click({ timeout: 1000 });
await expect(page.getByText('Delete').nth(3)).toBeVisible();
await page.getByText('Delete').nth(3).click();
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).not.toContainText('test text');
});
test('creating an issue and attaching it to a feedback succeeds', async ({
page,
}) => {
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
await page.getByRole('radio', { name: 'Issue' }).click();
await page.getByRole('button', { name: 'Create Issue' }).click();
await page.waitForTimeout(1000);
await page.getByPlaceholder('Please enter.').first().fill('test_issue');
await page.getByRole('button', { name: 'Save' }).click();
await page.waitForTimeout(1000);
await page.getByRole('radio', { name: 'Feedback' }).click();
await page.waitForURL(/.*channelId.*/, { timeout: 1000 });
await expect(page.getByText('There is no data yet')).toBeVisible();
const url = new URL(page.url());
const pathname = url.pathname;
const segments = pathname.split('/');
const projectId = segments[3];
const params = new URLSearchParams(url.search);
const channelId = params.get('channelId');
await axios.post(
`http://localhost:4000/api/projects/${projectId}/channels/${channelId}/feedbacks`,
{
message: 'test text',
},
{ headers: { 'x-api-key': 'MASTER_API_KEY' } },
);
await page.goto(
`http://localhost:3000/main/project/${projectId}/feedback?channelId=${channelId}`,
{ waitUntil: 'domcontentloaded' },
);
await page.waitForTimeout(2000);
await expect(page.locator('tbody')).toContainText('test text');
await page.getByRole('button', { name: '+' }).first().click();
const testIssue = page.getByText('test_issue').first();
await expect(testIssue).toBeVisible();
await testIssue.click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: '+' }).first().click();
await page.getByText('test text').first().click();
await page.getByRole('button', { name: '+' }).first().click();
await page.waitForTimeout(1000);
await page.keyboard.insertText('test_issue2');
await page.getByText('Create', { exact: true }).click();
await page.waitForTimeout(1000);
await page.keyboard.press('Enter');
await page.waitForTimeout(1000);
});
});
};
@@ -0,0 +1,71 @@
import { expect, test } from '@playwright/test';
import axios from 'axios';
const NUMBER_OF_FEEDBACKS = 25;
export default () => {
test.describe('create-multiple-feedbacks suite', () => {
test.afterEach(async ({ page }) => {
await page.getByRole('checkbox').nth(1).click();
await page.getByText('Delete Feedback').click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(1000);
await page.getByRole('checkbox').first().click();
await page.getByText('Delete Feedback').click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForTimeout(1000);
});
test('creating multiple feedbacks succeeds', async ({ page }) => {
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
await page.getByRole('radio', { name: 'Feedback' }).click();
await page.waitForURL(/.*channelId.*/, { timeout: 10000 });
await expect(page.getByText('There is no data yet')).toBeVisible();
const url = new URL(page.url());
const pathname = url.pathname;
const segments = pathname.split('/');
const projectId = segments[3];
const params = new URLSearchParams(url.search);
const channelId = params.get('channelId');
for (let i = 0; i < NUMBER_OF_FEEDBACKS; i++) {
await axios.post(
`http://localhost:4000/api/projects/${projectId}/channels/${channelId}/feedbacks`,
{
message: `test text ${i}`,
},
{ headers: { 'x-api-key': 'MASTER_API_KEY' } },
);
}
await page.goto(
`http://localhost:3000/main/project/${projectId}/feedback?channelId=${channelId}`,
{ waitUntil: 'domcontentloaded' },
);
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).toContainText('test text');
await expect(page.getByText('Page 1 of 2')).toBeVisible();
await page.getByRole('button', { name: '20', exact: true }).click();
await page.waitForTimeout(1000);
await page.getByRole('menuitem', { name: '30', exact: true }).click();
await page.waitForTimeout(1000);
});
});
};
+39
View File
@@ -0,0 +1,39 @@
import { expect, test } from '@playwright/test';
export default () => {
test('creating a project succeeds', async ({ page }) => {
await page.goto('http://localhost:3000/main/project/create', {
waitUntil: 'domcontentloaded',
});
await expect(page.getByRole('button', { name: 'Next' })).toBeVisible({
timeout: 1000,
});
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('button', { name: 'Next' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();
await page.locator("input[name='name']").click();
await page.locator("input[name='name']").fill('TestProject');
await page.getByRole('button', { name: 'Next' }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Next' }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Complete' }).click();
await page.waitForTimeout(1000);
await expect(
page.getByRole('heading', {
level: 2,
name: /project creation complete/i,
}),
).toBeVisible();
await expect(page.locator("input[name='name']")).toHaveValue('TestProject');
await page.getByRole('button', { name: 'Later' }).click();
});
};
+102
View File
@@ -0,0 +1,102 @@
import { expect, test } from '@playwright/test';
import axios from 'axios';
export default () => {
test.describe('search-feedback suite', () => {
test('creating and searching a feedback succeed', async ({ page }) => {
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
await page.getByRole('radio', { name: 'Feedback' }).click();
await page.waitForURL(/.*channelId.*/, { timeout: 10000 });
await expect(page.getByText('There is no data yet')).toBeVisible();
const url = new URL(page.url());
const pathname = url.pathname;
const segments = pathname.split('/');
const projectId = segments[3];
const params = new URLSearchParams(url.search);
const channelId = params.get('channelId');
await axios.post(
`http://localhost:4000/api/projects/${projectId}/channels/${channelId}/feedbacks`,
{
message: 'test text1',
},
{ headers: { 'x-api-key': 'MASTER_API_KEY' } },
);
await axios.post(
`http://localhost:4000/api/projects/${projectId}/channels/${channelId}/feedbacks`,
{
message: 'test text2',
},
{ headers: { 'x-api-key': 'MASTER_API_KEY' } },
);
await page.goto(
`http://localhost:3000/main/project/${projectId}/feedback?channelId=${channelId}`,
{ waitUntil: 'domcontentloaded' },
);
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).toContainText('test text1');
await expect(page.locator('tbody')).toContainText('test text2');
await page.getByText('Filter').click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'ID' }).click();
await page.waitForTimeout(1000);
await page.getByRole('option', { name: 'message' }).click();
await page.getByPlaceholder('Please enter').fill('test text1');
await page.getByRole('button', { name: 'Confirm' }).click();
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).toContainText('test text1');
await expect(page.locator('tbody')).not.toContainText('test text2');
await page.waitForTimeout(1000);
await page.getByText('Filter').click();
await page.waitForTimeout(1000);
await page.getByText('Add Filter').click();
await page.getByRole('button', { name: 'ID' }).click();
await page.waitForTimeout(1000);
await page.getByRole('option', { name: 'message' }).click();
await page.getByPlaceholder('Please enter').nth(1).fill('test text2');
await page.getByText('And', { exact: true }).click();
await page.waitForTimeout(1000);
await page.getByRole('option', { name: 'Or', exact: true }).click();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Confirm' }).click();
await page.waitForTimeout(1000);
await expect(page.locator('tbody')).toContainText('test text1');
await expect(page.locator('tbody')).toContainText('test text2');
await page.getByText('View').click();
await page.getByText('ID').nth(1).click();
await page.getByText('Created').nth(1).click();
await page.getByText('Updated').nth(1).click();
await page.getByText('Expand').first().click();
await page.waitForTimeout(1000);
await page.getByText('Expand').first().click();
await page.waitForTimeout(1000);
});
});
};
+19
View File
@@ -0,0 +1,19 @@
import { test } from '@playwright/test';
import createChannel from './scenarios/create-channel.spec';
import createFeedbackWithImage from './scenarios/create-feedback-with-image.spec';
import createFeedback from './scenarios/create-feedback.spec';
import createIssue from './scenarios/create-issue.spec';
import createMultipleFeedbacks from './scenarios/create-multiple-feedbacks.spec';
import createProject from './scenarios/create-project.spec';
import searchFeedback from './scenarios/search-feedback.spec';
test.describe('test suites', () => {
createProject();
createChannel();
createFeedback();
createFeedbackWithImage();
createMultipleFeedbacks();
createIssue();
searchFeedback();
});
+101
View File
@@ -0,0 +1,101 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import mysql from 'mysql2/promise';
export async function createConnection() {
return await mysql.createConnection({
host: '127.0.0.1',
port: 13307,
user: 'userfeedback',
password: 'userfeedback',
database: 'e2e',
});
}
export async function waitForDatabase(
maxRetries = 30,
delayMs = 1000,
): Promise<void> {
for (let i = 0; i < maxRetries; i++) {
try {
const connection = await createConnection();
await connection.ping();
await connection.end();
console.log('Database is ready');
return;
} catch (error) {
console.log(`Waiting for database... (${i + 1}/${maxRetries})`);
if (i === maxRetries - 1) {
throw new Error(
`Database is not available after ${maxRetries} retries`,
);
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}
export async function clearDatabaseTables(): Promise<void> {
const connection = await createConnection();
try {
await connection.execute('SET FOREIGN_KEY_CHECKS = 0');
const [tables] = (await connection.execute(
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'e2e' AND TABLE_TYPE = 'BASE TABLE'",
)) as [mysql.RowDataPacket[], mysql.FieldPacket[]];
for (const table of tables) {
const tableName = table.TABLE_NAME;
if (tableName !== 'migrations') {
console.log(`Clearing table: ${tableName}`);
await connection.execute(`DELETE FROM \`${tableName}\``);
await connection.execute(
`ALTER TABLE \`${tableName}\` AUTO_INCREMENT = 1`,
);
}
}
await connection.execute('SET FOREIGN_KEY_CHECKS = 1');
console.log('Database tables cleared');
} catch (error) {
console.log('Error clearing database tables:', error);
throw error;
} finally {
await connection.end();
}
}
export async function initializeDatabaseForTest(): Promise<void> {
try {
await waitForDatabase();
await clearDatabaseTables();
console.log('Database initialized for test');
} catch (error) {
console.error('Failed to initialize database:', error);
throw error;
}
}
export async function cleanupDatabaseAfterTest(): Promise<void> {
try {
await clearDatabaseTables();
console.log('Database cleaned up after test');
} catch (error) {
console.error('Failed to cleanup database:', error);
}
}
+114
View File
@@ -0,0 +1,114 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { Client } from '@opensearch-project/opensearch';
export interface OpenSearchConfig {
node: string;
username?: string;
password?: string;
}
export async function createOpenSearchClient(
config: OpenSearchConfig,
): Promise<Client> {
const clientConfig: any = {
node: config.node,
};
if (config.username && config.password) {
clientConfig.auth = {
username: config.username,
password: config.password,
};
}
return new Client(clientConfig);
}
export async function waitForOpenSearch(
client: Client,
maxRetries = 30,
delayMs = 1000,
): Promise<void> {
for (let i = 0; i < maxRetries; i++) {
try {
await client.ping();
console.log('OpenSearch is ready');
return;
} catch (error) {
console.log(`Waiting for OpenSearch... (${i + 1}/${maxRetries})`);
if (i === maxRetries - 1) {
throw new Error(
`OpenSearch is not available after ${maxRetries} retries`,
);
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}
export async function clearOpenSearchIndices(client: Client): Promise<void> {
try {
const response = await client.cat.indices({ format: 'json' });
const indices = response.body as Array<{ index: string }>;
const channelIndices = indices
.filter((index) => index.index.startsWith('channel_'))
.map((index) => index.index);
if (channelIndices.length > 0) {
console.log(`Deleting OpenSearch indices: ${channelIndices.join(', ')}`);
await client.indices.delete({ index: channelIndices });
console.log('OpenSearch indices cleared');
} else {
console.log('No channel indices found in OpenSearch');
}
} catch (error) {
console.log('Error clearing OpenSearch indices:', error);
}
}
export async function initializeOpenSearchForTest(): Promise<void> {
const config: OpenSearchConfig = {
node: 'http://localhost:9200',
};
const client = await createOpenSearchClient(config);
try {
await waitForOpenSearch(client);
await clearOpenSearchIndices(client);
console.log('OpenSearch initialized for test');
} catch (error) {
console.error('Failed to initialize OpenSearch:', error);
throw error;
}
}
export async function cleanupOpenSearchAfterTest(): Promise<void> {
const config: OpenSearchConfig = {
node: 'http://localhost:9200',
};
const client = await createOpenSearchClient(config);
try {
await clearOpenSearchIndices(client);
console.log('OpenSearch cleaned up after test');
} catch (error) {
console.error('Failed to cleanup OpenSearch:', error);
}
}