Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Tests

on:
push:
branches: ['main']
pull_request:
branches: ['main']

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
api-tests:
runs-on: ubuntu-latest
timeout-minutes: 30

services:
postgres:
image: postgres:17
env:
POSTGRES_USER: devtable
POSTGRES_PASSWORD: devtable
POSTGRES_DB: devtable_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379

env:
DB_HOST: localhost
DB_PORT: 5432
DB_USERNAME: devtable
DB_PASSWORD: devtable
DB_DATABASE: devtable_test
INTEGRATION_TEST_PG_URL: postgresql://devtable:devtable@localhost:5432/devtable_test_integration
END_2_END_TEST_PG_URL: postgresql://devtable:devtable@localhost:5432/devtable_test_e2e
REDIS_URL: redis://localhost:6379
SECRET_KEY: test-secret-key-for-ci
NODE_ENV: test

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'

- name: Cache Nx
uses: actions/cache@v4
with:
path: .nx
key: ${{ runner.os }}-nx-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-nx-

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Create test databases
run: |
PGPASSWORD=devtable psql -h localhost -U devtable -d devtable_test -c "CREATE DATABASE devtable_test_integration;"
PGPASSWORD=devtable psql -h localhost -U devtable -d devtable_test -c "CREATE DATABASE devtable_test_e2e;"

- uses: nrwl/nx-set-shas@v3

- name: Run API unit tests
working-directory: ./api
run: yarn test:u

- name: Run API integration tests
working-directory: ./api
run: yarn test:i

- name: Run API e2e tests
working-directory: ./api
run: yarn test:e2e

- name: Upload coverage reports
uses: codecov/codecov-action@v4
with:
files: ./api/coverage/*/lcov.info
flags: api
fail_ci_if_error: false
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ api/.jest_cache
api/coverage*
api/yarn.lock
.nx/cache
.nx/workspace-data
.nx/workspace-data
.vscode/
12 changes: 0 additions & 12 deletions .vscode/settings.json

This file was deleted.

8 changes: 5 additions & 3 deletions api/src/services/datasource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,12 @@ export class DataSourceService {
const source = new Source(configuration);
try {
await source.initialize();
await source.destroy();
} catch (error) {
const message = error.message ?? translate('DATASOURCE_CONNECTION_TEST_FAILED', locale);
throw new ApiError(BAD_REQUEST, { message });
// Log the original error for debugging purposes
console.error('Database connection test failed:', error.message);
// Always use the generic error message to avoid exposing internal connection details
throw new ApiError(BAD_REQUEST, { message: translate('DATASOURCE_CONNECTION_TEST_FAILED', locale) });
}
await source.destroy();
}
}
2 changes: 1 addition & 1 deletion api/src/services/job.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export class JobService {
}
jobs = await jobRepo
.createQueryBuilder('job')
.where('type = :type', { type: JobType.RENAME_DATASOURCE })
.where('type = :type', { type: JobType.FIX_DASHBOARD_PERMISSION })
.andWhere('status = :status', { status: JobStatus.INIT })
.orderBy('create_time', 'ASC')
.getMany();
Expand Down
21 changes: 9 additions & 12 deletions api/tests/e2e/06_query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,38 @@ describe('QueryController', () => {
id: 'pgQuery',
type: 'postgresql',
key: 'preset',
sql: 'SELECT ${sql_snippets.role_columns} FROM role WHERE id = ${filters.role_id} AND ${context.true}',
config: { _type: 'postgresql', sql: 'SELECT ${sql_snippets.role_columns} FROM role WHERE id = ${filters.role_id} AND ${context.true}' },
pre_process: '',
},
{
id: 'httpGetQuery',
type: 'http',
key: 'jsonplaceholder_renamed',
sql: '',
config: { _type: 'http', react_to: [] },
pre_process:
'function build_request({ context, filters }, utils) {\n const data = {};\n const headers = { "Content-Type": "application/json" };\n\n return {\n method: "GET",\n url: "/posts/1",\n params: {},\n headers,\n data,\n };\n}\n',
},
{
id: 'httpPostQuery',
type: 'http',
key: 'jsonplaceholder_renamed',
sql: '',
config: { _type: 'http', react_to: [] },
pre_process:
'function build_request({ context, filters }, utils) {\n const data = { "title": "foo", "body": "bar", "userId": 1 };\n const headers = { "Content-Type": "application/json" };\n\n return {\n method: "POST",\n url: "/posts",\n params: {},\n headers,\n data,\n };\n}\n',
},
{
id: 'httpPutQuery',
type: 'http',
key: 'jsonplaceholder_renamed',
sql: '',
config: { _type: 'http', react_to: [] },
pre_process:
'function build_request({ context, filters }, utils) {\n const data = { "id": 1, "title": "foo", "body": "bar", "userId": 1 };\n const headers = { "Content-Type": "application/json" };\n\n return {\n method: "PUT",\n url: "/posts/1",\n params: {},\n headers,\n data,\n };\n}\n',
},
{
id: 'httpDeleteQuery',
type: 'http',
key: 'jsonplaceholder_renamed',
sql: '',
config: { _type: 'http', react_to: [] },
pre_process:
'function build_request({ context, filters }, utils) {\n const data = {};\n const headers = { "Content-Type": "application/json" };\n\n return {\n method: "DELETE",\n url: "/posts/1",\n params: {},\n headers,\n data,\n };\n}\n',
},
Expand Down Expand Up @@ -238,13 +238,10 @@ describe('QueryController', () => {
.post('/query/structure')
.set('Authorization', `Bearer ${superadminLogin.token}`)
.send(query);

expect(response.body.length).toEqual(222);
expect(response.body[212]).toMatchObject({
table_schema: 'public',
table_name: 'dashboard',
table_type: 'BASE TABLE',
});
// Filter to only public schema tables to avoid version-specific system table counts
const publicTables = response.body.filter((t: any) => t.table_schema === 'public');
expect(publicTables.length).toEqual(14);
expect(publicTables).toMatchSnapshot();
});

it('query_type = COLUMNS', async () => {
Expand Down
76 changes: 76 additions & 0 deletions api/tests/e2e/__snapshots__/06_query.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`QueryController queryStructure query_type = TABLES 1`] = `
[
{
"table_name": "account",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "api_key",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "config",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "custom_function",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "dashboard",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "dashboard_changelog",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "dashboard_content",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "dashboard_content_changelog",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "dashboard_permission",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "data_source",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "job",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "role",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "schema_migrations",
"table_schema": "public",
"table_type": "BASE TABLE",
},
{
"table_name": "sql_snippet",
"table_schema": "public",
"table_type": "BASE TABLE",
},
]
`;
9 changes: 3 additions & 6 deletions api/tests/integration/06_query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,9 @@ describe('QueryService', () => {
describe('queryStructure', () => {
it('TABLES', async () => {
const results = await queryService.queryStructure('TABLES', 'postgresql', 'pg', '', '');
expect(results.length).toEqual(222);
expect(results[212]).toMatchObject({
table_schema: 'public',
table_name: 'dashboard',
table_type: 'BASE TABLE',
});
const tablesInPublicSchema = results.filter((result) => result.table_schema === 'public');
expect(tablesInPublicSchema.length).toEqual(14);
expect(tablesInPublicSchema).toMatchSnapshot();
});

it('COLUMNS', async () => {
Expand Down
12 changes: 6 additions & 6 deletions api/tests/integration/12_dashboard_content_changelog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('DashboardContentChangelogService', () => {
expect(results.data[0].diff).toContain('--- a/data.json\n' + '+++ b/data.json');
expect(results.data[0].diff).toContain('@@ -8,7 +8,7 @@');
expect(results.data[0].diff).toContain('-\t\t\t\t\t"key": "pg",\n' + '+\t\t\t\t\t"key": "pg_renamed",\n');
expect(results.data[0].diff).toContain('@@ -49,5 +49,9 @@');
expect(results.data[0].diff).toContain('@@ -64,5 +64,9 @@');
expect(results.data[0].diff).toContain(
'-\t}\n' +
'+\t},\n' +
Expand All @@ -38,7 +38,7 @@ describe('DashboardContentChangelogService', () => {
expect(results.data[1].diff).toContain('--- a/data.json\n' + '+++ b/data.json');
expect(results.data[1].diff).toContain('@@ -8,7 +8,7 @@');
expect(results.data[1].diff).toContain('-\t\t\t\t\t"key": "pg",\n' + '+\t\t\t\t\t"key": "pg_renamed",\n');
expect(results.data[1].diff).toContain('@@ -23,5 +23,9 @@');
expect(results.data[1].diff).toContain('@@ -29,5 +29,9 @@');
expect(results.data[1].diff).toContain(
'-\t}\n' +
'+\t},\n' +
Expand All @@ -51,14 +51,14 @@ describe('DashboardContentChangelogService', () => {

expect(results.data[2].diff).toContain('diff --git a/data.json b/data.json');
expect(results.data[2].diff).toContain('--- a/data.json\n' + '+++ b/data.json');
expect(results.data[2].diff).toContain('@@ -15,28 +15,28 @@');
expect(results.data[2].diff).toContain('@@ -18,7 +18,7 @@');
expect(results.data[2].diff).toContain(
'-\t\t\t\t\t"key": "jsonplaceholder",\n' + '+\t\t\t\t\t"key": "jsonplaceholder_renamed",\n',
);

expect(results.data[3].diff).toContain('diff --git a/data.json b/data.json');
expect(results.data[3].diff).toContain('--- a/data.json\n' + '+++ b/data.json');
expect(results.data[3].diff).toContain('@@ -15,7 +15,7 @@');
expect(results.data[3].diff).toContain('@@ -18,7 +18,7 @@');
expect(results.data[3].diff).toContain(
'-\t\t\t\t\t"key": "jsonplaceholder",\n' + '+\t\t\t\t\t"key": "jsonplaceholder_renamed",\n',
);
Expand All @@ -80,7 +80,7 @@ describe('DashboardContentChangelogService', () => {
expect(results.data[0].diff).toContain('--- a/data.json\n' + '+++ b/data.json');
expect(results.data[0].diff).toContain('@@ -8,7 +8,7 @@');
expect(results.data[0].diff).toContain('-\t\t\t\t\t"key": "pg",\n' + '+\t\t\t\t\t"key": "pg_renamed",\n');
expect(results.data[0].diff).toContain('@@ -49,5 +49,9 @@');
expect(results.data[0].diff).toContain('@@ -64,5 +64,9 @@');
expect(results.data[0].diff).toContain(
'-\t}\n' +
'+\t},\n' +
Expand All @@ -93,7 +93,7 @@ describe('DashboardContentChangelogService', () => {

expect(results.data[1].diff).toContain('diff --git a/data.json b/data.json');
expect(results.data[1].diff).toContain('--- a/data.json\n' + '+++ b/data.json');
expect(results.data[1].diff).toContain('@@ -15,28 +15,28 @@');
expect(results.data[1].diff).toContain('@@ -18,7 +18,7 @@');
expect(results.data[1].diff).toContain(
'-\t\t\t\t\t"key": "jsonplaceholder",\n' + '+\t\t\t\t\t"key": "jsonplaceholder_renamed",\n',
);
Expand Down
Loading
Loading