Merge branch 'feature/portal-test'

This commit is contained in:
SPRINX0\prochazka
2025-01-07 13:39:18 +01:00
28 changed files with 31914 additions and 39 deletions

View File

@@ -31,9 +31,9 @@ describe('Add connection', () => {
cy.visit('http://localhost:3000'); cy.visit('http://localhost:3000');
cy.contains('Connections'); cy.contains('Connections');
cy.realPress('F1'); // cy.realPress('F1');
cy.realType('Close all'); // cy.realType('Close all');
cy.realPress('Enter'); // cy.realPress('Enter');
cy.get('[data-testid=ConnectionList_buttonNewConnection]').click(); cy.get('[data-testid=ConnectionList_buttonNewConnection]').click();
cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL'); cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL');
@@ -62,9 +62,9 @@ describe('Add connection', () => {
cy.visit('http://localhost:3000'); cy.visit('http://localhost:3000');
cy.contains('Connections'); cy.contains('Connections');
cy.realPress('F1'); // cy.realPress('F1');
cy.realType('Close all'); // cy.realType('Close all');
cy.realPress('Enter'); // cy.realPress('Enter');
cy.get('[data-testid=ConnectionList_buttonNewConnection]').click(); cy.get('[data-testid=ConnectionList_buttonNewConnection]').click();
cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL'); cy.get('[data-testid=ConnectionDriverFields_connectionType]').select('MySQL');

View File

@@ -1,8 +1,89 @@
describe('Run as portal', () => { describe('Run as portal', () => {
it('successfully loads', () => { it('successfully loads', () => {
cy.visit('http://localhost:3000'); cy.visit('http://localhost:3000');
cy.contains('MySql'); cy.contains('MySql-connection');
cy.contains('Postgres'); cy.contains('Postgres-connection');
});
// it('Delete chinook', () => {
// cy.visit('http://localhost:3000');
// cy.contains('MySql-connection').rightclick();
// cy.contains('New Query (server)').click();
// cy.realType('drop database if exists Chinook');
// cy.realPress('F5');
// cy.contains('Query execution finished');
// cy.contains('Postgres-connection').rightclick();
// cy.contains('New Query (server)').click();
// cy.realType('drop database if exists "Chinook"');
// cy.realPress('F5');
// cy.contains('Query execution finished');
// // cy.realPress('F1');
// // cy.realType('Close all');
// // cy.realPress('Enter');
// });
it('Create Chinook MySQL', () => {
cy.visit('http://localhost:3000');
cy.contains('MySql-connection').click();
cy.contains('MySql-connection').rightclick();
cy.contains('Create database').click();
cy.get('[data-testid=InputTextModal_value]').clear().type('Chinook');
cy.get('[data-testid=InputTextModal_ok]').click();
});
it('Create Chinook Postgres', () => {
cy.visit('http://localhost:3000');
cy.contains('Postgres-connection').click();
cy.contains('Postgres-connection').rightclick();
cy.contains('Create database').click();
cy.get('[data-testid=InputTextModal_value]').clear().type('Chinook');
cy.get('[data-testid=InputTextModal_ok]').click();
});
it('Import Chinook MySQL', () => {
cy.visit('http://localhost:3000');
cy.contains('MySql-connection').click();
cy.get('[data-testid=DatabaseAppObject_Chinook]').rightclick();
cy.contains('Chinook').rightclick();
cy.contains('Restore/import SQL dump').click();
cy.get('#uploadFileButton').selectFile('data/chinook-mysql.sql', { force: true });
cy.wait(500);
cy.get('[data-testid=ImportDatabaseDumpModal_runImport]').click();
cy.contains('Importing database');
cy.contains('Finished job script');
cy.get('[data-testid=RunScriptModal_close]').click();
cy.contains('Chinook').click();
cy.contains('Album');
});
it('Import Chinook Postgres', () => {
cy.visit('http://localhost:3000');
cy.contains('Postgres-connection').click();
cy.get('[data-testid=DatabaseAppObject_Chinook]').rightclick();
cy.contains('Restore/import SQL dump').click();
cy.get('#uploadFileButton').selectFile('data/chinook-postgres.sql', { force: true });
cy.wait(500);
cy.get('[data-testid=ImportDatabaseDumpModal_runImport]').click();
cy.contains('Importing database');
cy.contains('Finished job script');
cy.get('[data-testid=RunScriptModal_close]').click();
cy.contains('Chinook').click();
cy.contains('album');
});
it('Open ask pwd connection', () => {
cy.visit('http://localhost:3000');
cy.contains('Postgres-ask-connection').click();
cy.testid('DatabaseLoginModal_username').clear().type('postgres');
cy.testid('DatabaseLoginModal_password').clear().type('Pwd2020Db');
cy.testid('DatabaseLoginModal_connect').click();
cy.contains('Chinook').click();
cy.contains('album');
}); });
// it('import chinook DB', () => { // it('import chinook DB', () => {

View File

@@ -22,4 +22,8 @@
// //
// //
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
Cypress.Commands.add('testid', (testId, options = {}) => {
return cy.get(`[data-testid="${testId}"]`, options);
});

View File

@@ -14,9 +14,31 @@
// *********************************************************** // ***********************************************************
// Import commands.js using ES2015 syntax: // Import commands.js using ES2015 syntax:
import './commands' import './commands';
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')
import "cypress-real-events"; import 'cypress-real-events';
beforeEach(() => {
// Replace 'my-database-name' with the actual IndexedDB name
cy.window().then(win => {
return new Promise((resolve, reject) => {
const request = win.indexedDB.deleteDatabase('localforage');
request.onsuccess = () => {
// Database successfully deleted
resolve();
};
request.onerror = () => {
// Some error occurred
reject(request.error);
};
request.onblocked = () => {
// Might happen if there are open connections
console.warn('IndexedDB deletion blocked');
resolve();
};
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,22 @@
CONNECTIONS=mysql,postgres CONNECTIONS=mysql,postgres
LABEL_mysql=MySql LABEL_mysql=MySql-connection
SERVER_mysql=mariadb SERVER_mysql=mysql
USER_mysql=root USER_mysql=root
PASSWORD_mysql=Pwd2020Db PASSWORD_mysql=Pwd2020Db
PORT_mysql=3306 PORT_mysql=3306
ENGINE_mysql=mysql@dbgate-plugin-mysql ENGINE_mysql=mysql@dbgate-plugin-mysql
LABEL_postgres=Postgres LABEL_postgres=Postgres-connection
SERVER_postgres=postgres SERVER_postgres=postgres
USER_postgres=postgres USER_postgres=postgres
PASSWORD_postgres=Pwd2020Db PASSWORD_postgres=Pwd2020Db
PORT_postgres=5432 PORT_postgres=5432
ENGINE_postgres=postgres@dbgate-plugin-postgres ENGINE_postgres=postgres@dbgate-plugin-postgres
LABEL_pgask=Postgres-ask-connection
SERVER_pgask=postgres
USER_pgask=postgres
PORT_pgask=5432
ENGINE_pgask=postgres@dbgate-plugin-postgres
PASSWORD_MODE_pgask=askUser

View File

@@ -1,15 +1,22 @@
CONNECTIONS=mysql,postgres CONNECTIONS=mysql,postgres,pgask
LABEL_mysql=MySql LABEL_mysql=MySql-connection
SERVER_mysql=localhost SERVER_mysql=localhost
USER_mysql=root USER_mysql=root
PASSWORD_mysql=Pwd2020Db PASSWORD_mysql=Pwd2020Db
PORT_mysql=16004 PORT_mysql=16004
ENGINE_mysql=mysql@dbgate-plugin-mysql ENGINE_mysql=mysql@dbgate-plugin-mysql
LABEL_postgres=Postgres LABEL_postgres=Postgres-connection
SERVER_postgres=localhost SERVER_postgres=localhost
USER_postgres=postgres USER_postgres=postgres
PASSWORD_postgres=Pwd2020Db PASSWORD_postgres=Pwd2020Db
PORT_postgres=16000 PORT_postgres=16000
ENGINE_postgres=postgres@dbgate-plugin-postgres ENGINE_postgres=postgres@dbgate-plugin-postgres
LABEL_pgask=Postgres-ask-connection
SERVER_pgask=localhost
USER_pgask=postgres
PORT_pgask=16000
ENGINE_pgask=postgres@dbgate-plugin-postgres
PASSWORD_MODE_pgask=askUser

32
e2e-tests/init/portal.js Normal file
View File

@@ -0,0 +1,32 @@
const dbgateApi = require('dbgate-api');
dbgateApi.initializeApiEnvironment();
const dbgatePluginMysql = require('dbgate-plugin-mysql');
dbgateApi.registerPlugins(dbgatePluginMysql);
const dbgatePluginPostgres = require('dbgate-plugin-postgres');
dbgateApi.registerPlugins(dbgatePluginPostgres);
async function run() {
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_mysql,
user: process.env.USER_mysql,
password: process.env.PASSWORD_mysql,
port: process.env.PORT_mysql,
engine: 'mysql@dbgate-plugin-mysql',
},
sql: 'drop database if exists Chinook',
});
await dbgateApi.executeQuery({
connection: {
server: process.env.SERVER_postgres,
user: process.env.USER_postgres,
password: process.env.PASSWORD_postgres,
port: process.env.PORT_postgres,
engine: 'postgres@dbgate-plugin-postgres',
},
sql: 'drop database if exists "Chinook"',
});
}
dbgateApi.runScript(run);

View File

@@ -23,8 +23,8 @@
"start:add-connection": "cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests", "start:add-connection": "cd .. && node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:portal:local": "cd .. && env-cmd -f e2e-tests/env/portal-local/.env node packer/build/bundle.js --listen-api --run-e2e-tests", "start:portal:local": "cd .. && env-cmd -f e2e-tests/env/portal-local/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal-local/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"start:portal:ci": "cd .. && env-cmd -f e2e-tests/env/portal-ci/.env node packer/build/bundle.js --listen-api --run-e2e-tests", "start:portal:ci": "cd .. && env-cmd -f e2e-tests/env/portal-ci/.env node e2e-tests/init/portal.js && env-cmd -f e2e-tests/env/portal-ci/.env node packer/build/bundle.js --listen-api --run-e2e-tests",
"test:ci": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection:ci && start-server-and-test start:portal:ci http://localhost:3000 cy:run:portal:ci", "test:ci": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection:ci && start-server-and-test start:portal:ci http://localhost:3000 cy:run:portal:ci",
"test:local": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection:local && start-server-and-test start:portal:local http://localhost:3000 cy:run:portal:local" "test:local": "start-server-and-test start:add-connection http://localhost:3000 cy:run:add-connection:local && start-server-and-test start:portal:local http://localhost:3000 cy:run:portal:local"

View File

@@ -222,7 +222,7 @@ module.exports = {
}, },
test_meta: true, test_meta: true,
test({ connection, requestDbList }) { test({ connection, requestDbList = false }) {
const subprocess = fork( const subprocess = fork(
global['API_PACKAGE'] || process.argv[1], global['API_PACKAGE'] || process.argv[1],
[ [
@@ -267,7 +267,7 @@ module.exports = {
} }
if (test) { if (test) {
const testRes = await this.test(res); const testRes = await this.test({ connection: res });
if (testRes.msgtype == 'connected') { if (testRes.msgtype == 'connected') {
volatileConnections[res._id] = res; volatileConnections[res._id] = res;
return { return {

View File

@@ -97,7 +97,7 @@ function packagedPluginsDir() {
// } // }
} }
if (processArgs.runE2eTests) { if (processArgs.runE2eTests) {
return path.resolve('packer/build/plugins'); return path.resolve('packer/build/plugins');
} }
return null; return null;
} }
@@ -113,7 +113,7 @@ function getPluginBackendPath(packageName) {
return path.join(packagedPluginsDir(), packageName, 'dist', 'backend.js'); return path.join(packagedPluginsDir(), packageName, 'dist', 'backend.js');
} }
const res = path.join(pluginsdir(), packageName, 'dist', 'backend.js') const res = path.join(pluginsdir(), packageName, 'dist', 'backend.js');
if (fs.existsSync(res)) { if (fs.existsSync(res)) {
return res; return res;
} }

View File

@@ -1,3 +1,5 @@
const path = require('path');
function getNamedArg(name) { function getNamedArg(name) {
const argIndex = process.argv.indexOf(name); const argIndex = process.argv.indexOf(name);
if (argIndex > 0) { if (argIndex > 0) {
@@ -20,13 +22,15 @@ function getPassArgs() {
const res = []; const res = [];
if (global['PLUGINS_DIR']) { if (global['PLUGINS_DIR']) {
res.push('--plugins-dir', global['PLUGINS_DIR']); res.push('--plugins-dir', global['PLUGINS_DIR']);
} else if (runE2eTests) {
res.push('--plugins-dir', path.resolve('packer/build/plugins'));
} }
if (listenApiChild) { if (listenApiChild) {
res.push('listen-api-child'); res.push('listen-api-child');
} }
if (runE2eTests) { // if (runE2eTests) {
res.push('--run-e2e-tests'); // res.push('--run-e2e-tests');
} // }
return res; return res;
} }

View File

@@ -34,6 +34,7 @@
export let disableBoldScroll = false; export let disableBoldScroll = false;
export let filter = null; export let filter = null;
export let disableHover = false; export let disableHover = false;
export let divProps = {};
$: isChecked = $: isChecked =
checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x)); checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x));
@@ -109,6 +110,7 @@
on:dragend on:dragend
on:drop on:drop
bind:this={domDiv} bind:this={domDiv}
{...divProps}
> >
{#if checkedObjectsStore} {#if checkedObjectsStore}
<CheckboxField <CheckboxField

View File

@@ -284,6 +284,7 @@
icon: 'img sql-file', icon: 'img sql-file',
tooltip, tooltip,
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
focused: true,
props: { props: {
conid: data._id, conid: data._id,
}, },
@@ -320,7 +321,7 @@
], ],
{ divider: true }, { divider: true },
!data.singleDatabase && [ !data.singleDatabase && [
hasPermission(`dbops/query`) && { onClick: handleNewQuery, text: 'New query', isNewQuery: true }, hasPermission(`dbops/query`) && { onClick: handleNewQuery, text: 'New Query (server)', isNewQuery: true },
$openedConnections.includes(data._id) && $openedConnections.includes(data._id) &&
data.status && { data.status && {
text: 'Refresh', text: 'Refresh',

View File

@@ -53,6 +53,7 @@
icon: 'img sql-file', icon: 'img sql-file',
tooltip, tooltip,
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
focused: true,
props: { props: {
conid: connection._id, conid: connection._id,
database: name, database: name,
@@ -240,6 +241,7 @@
title: 'Query #', title: 'Query #',
icon: 'img query-design', icon: 'img query-design',
tabComponent: 'QueryDesignTab', tabComponent: 'QueryDesignTab',
focused: true,
props: { props: {
conid: connection._id, conid: connection._id,
database: name, database: name,
@@ -573,4 +575,7 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
isChoosed={data.connection?._id == $focusedConnectionOrDatabase?.conid && isChoosed={data.connection?._id == $focusedConnectionOrDatabase?.conid &&
data.name == $focusedConnectionOrDatabase?.database} data.name == $focusedConnectionOrDatabase?.database}
disableBoldScroll={!!$focusedConnectionOrDatabase} disableBoldScroll={!!$focusedConnectionOrDatabase}
divProps={{
'data-testid': `DatabaseAppObject_${data.name}`,
}}
/> />

View File

@@ -421,6 +421,7 @@
title: 'Query #', title: 'Query #',
icon: 'img query-design', icon: 'img query-design',
tabComponent: 'QueryDesignTab', tabComponent: 'QueryDesignTab',
focused: true,
props: { props: {
conid: data.conid, conid: data.conid,
database: data.database, database: data.database,
@@ -666,6 +667,7 @@
{ {
// title: getObjectTitle(connection, schemaName, pureName), // title: getObjectTitle(connection, schemaName, pureName),
title: tabComponent ? getObjectTitle(connection, schemaName, pureName) : 'Query #', title: tabComponent ? getObjectTitle(connection, schemaName, pureName) : 'Query #',
focused: tabComponent == null,
tooltip, tooltip,
icon: icon:
icon || icon ||

View File

@@ -216,6 +216,7 @@
title: 'Query #', title: 'Query #',
icon: 'img sql-file', icon: 'img sql-file',
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
focused: true,
props: { props: {
conid, conid,
database, database,

View File

@@ -153,6 +153,7 @@
title: 'Query #', title: 'Query #',
icon: 'img sql-file', icon: 'img sql-file',
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
focused: true,
props: { props: {
schemaName: display.baseTableOrSimilar?.schemaName, schemaName: display.baseTableOrSimilar?.schemaName,
pureName: display.baseTableOrSimilar?.pureName, pureName: display.baseTableOrSimilar?.pureName,

View File

@@ -107,6 +107,7 @@
disabled={passwordMode == 'askPassword'} disabled={passwordMode == 'askPassword'}
focused={passwordMode == 'askUser'} focused={passwordMode == 'askUser'}
saveOnInput saveOnInput
data-testid="DatabaseLoginModal_username"
/> />
<FormPasswordField <FormPasswordField
label="Password" label="Password"
@@ -114,6 +115,7 @@
autocomplete="current-password" autocomplete="current-password"
focused={passwordMode == 'askPassword'} focused={passwordMode == 'askPassword'}
saveOnInput saveOnInput
data-testid="DatabaseLoginModal_password"
/> />
{#if isTesting} {#if isTesting}
@@ -141,11 +143,11 @@
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
{#if isTesting} {#if isTesting}
<FormStyledButton value="Stop connecting" on:click={handleCancelTest} /> <FormStyledButton value="Stop connecting" on:click={handleCancelTest} data-testid="DatabaseLoginModal_stop" />
{:else} {:else}
<FormSubmit value="Connect" on:click={handleSubmit} /> <FormSubmit value="Connect" on:click={handleSubmit} data-testid="DatabaseLoginModal_connect" />
{/if} {/if}
<FormStyledButton value="Close" on:click={closeCurrentModal} /> <FormStyledButton value="Close" on:click={closeCurrentModal} data-testid="DatabaseLoginModal_close" />
</svelte:fragment> </svelte:fragment>
</ModalBase> </ModalBase>
</FormProviderCore> </FormProviderCore>

View File

@@ -113,8 +113,18 @@
</div> </div>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<FormSubmit value="Run import" on:click={e => handleSubmit(e.detail)} disabled={!inputFile} /> <FormSubmit
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} /> value="Run import"
on:click={e => handleSubmit(e.detail)}
disabled={!inputFile}
data-testid="ImportDatabaseDumpModal_runImport"
/>
<FormStyledButton
type="button"
value="Cancel"
on:click={closeCurrentModal}
data-testid="ImportDatabaseDumpModal_cancel"
/>
</svelte:fragment> </svelte:fragment>
</ModalBase> </ModalBase>
</FormProvider> </FormProvider>

View File

@@ -25,11 +25,11 @@
{header} {header}
</svelte:fragment> </svelte:fragment>
<FormTextField {label} name="value" focused /> <FormTextField {label} name="value" focused data-testid="InputTextModal_value" />
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} /> <FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} data-testid="InputTextModal_ok" />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} /> <FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} data-testid="InputTextModal_cancel" />
</svelte:fragment> </svelte:fragment>
</ModalBase> </ModalBase>
</FormProvider> </FormProvider>

View File

@@ -77,9 +77,9 @@
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
{#if isRunning} {#if isRunning}
<FormStyledButton value="Stop" on:click={handleStop} /> <FormStyledButton value="Stop" on:click={handleStop} data-testid="RunScriptModal_stop" />
{:else} {:else}
<FormStyledButton value="Close" on:click={handleClose} /> <FormStyledButton value="Close" on:click={handleClose} data-testid="RunScriptModal_close" />
{/if} {/if}
{#if onOpenResult && !isRunning} {#if onOpenResult && !isRunning}

View File

@@ -116,6 +116,7 @@
title: 'Query #', title: 'Query #',
icon: 'img sql-file', icon: 'img sql-file',
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
focused: true,
props: { props: {
conid, conid,
database, database,

View File

@@ -93,7 +93,7 @@ export const openedConnections = writable([]);
export const temporaryOpenedConnections = writable([]); export const temporaryOpenedConnections = writable([]);
export const openedSingleDatabaseConnections = writable([]); export const openedSingleDatabaseConnections = writable([]);
export const expandedConnections = writable([]); export const expandedConnections = writable([]);
export const currentDatabase = writableWithForage(null, 'currentDatabase'); export const currentDatabase = writableWithStorage(null, 'currentDatabase');
export const openedTabs = writableWithForage<TabDefinition[]>([], getOpenedTabsStorageName(), x => [...(x || [])]); export const openedTabs = writableWithForage<TabDefinition[]>([], getOpenedTabsStorageName(), x => [...(x || [])]);
export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat'); export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat');
export const extensions = writable<ExtensionsDirectory>(null); export const extensions = writable<ExtensionsDirectory>(null);

View File

@@ -166,7 +166,7 @@
const handleGenerateScript = async e => { const handleGenerateScript = async e => {
const values = $formValues as any; const values = $formValues as any;
const code = await createImpExpScript($extensions, values, undefined, true); const code = await createImpExpScript($extensions, values, true);
openNewTab( openNewTab(
{ {
title: 'Shell #', title: 'Shell #',

View File

@@ -161,6 +161,7 @@
icon: 'img sql-file', icon: 'img sql-file',
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
objectTypeField: appObjectData.objectTypeField, objectTypeField: appObjectData.objectTypeField,
focused: true,
props: { props: {
conid, conid,
database, database,

View File

@@ -57,6 +57,7 @@
title: 'Query #', title: 'Query #',
icon: 'icon sql-file', icon: 'icon sql-file',
tabComponent: 'QueryTab', tabComponent: 'QueryTab',
focused: true,
props: { props: {
conid: item.conid, conid: item.conid,
database: item.database, database: item.database,