Skip to content

🪟 🧪 E2E Tests for auto-detect schema changes #20682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5ff8d49
Add support to get workspaceId, create/delete source/destination from…
edmundito Dec 13, 2022
e751d9d
Test delete source
edmundito Dec 14, 2022
7f771e9
commands/request -> commands/api
edmundito Dec 14, 2022
b14f7eb
Add util to convert chains to promises, cleanup api, use async/await …
edmundito Dec 14, 2022
4758486
Add the ability to create and delete a connection
edmundito Dec 14, 2022
679a608
Add E2E test for non-breaking schema changes
edmundito Dec 19, 2022
ca7369a
Add test for breaking change auto-detect flow
edmundito Dec 20, 2022
ed7a9c8
Fix linting issues
edmundito Dec 20, 2022
32f40a0
Clean up afterEach for autoDetectSchema
edmundito Dec 20, 2022
ed9e38f
schemaChangesButton should be disabled after clicking
edmundito Dec 20, 2022
1f9fefa
Append random strings to source, dest, and connection created by auto…
edmundito Dec 20, 2022
aae4d3d
Add E2E tests to verify status icon in list pages
edmundito Dec 21, 2022
64f153c
Ensure that the manual sync button is enabled or disabled depending o…
edmundito Dec 21, 2022
c9336bb
Add test to verify non-breaking prefrence update
edmundito Dec 21, 2022
ca31816
Fix testid prop case in StatusCell
edmundito Jan 18, 2023
0fc7d57
Fix small issues in auto-detect schema tests
edmundito Jan 27, 2023
00309ef
Remove it.only from autoDetectSchema.spec
edmundito Jan 30, 2023
6b99bab
Remove promisification of api requests in e2e
edmundito Jan 30, 2023
96a712d
Fix typing in apiRequest
edmundito Jan 30, 2023
fd30bdd
Cleanup requestWorkspaceId
edmundito Jan 30, 2023
e06f4f5
Update auto-detect e2e test to check where backdrop should not exist …
edmundito Jan 30, 2023
096f5f3
Remove promise util for cypress
edmundito Jan 30, 2023
874f0e9
Fix column name in connection e2e spec
edmundito Jan 30, 2023
14a29c5
Fix fields in connection spec to match the new users table. Add email…
edmundito Jan 31, 2023
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
67 changes: 67 additions & 0 deletions airbyte-webapp-e2e-tests/cypress/commands/api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
ConnectionGetBody,
Connection,
ConnectionCreateRequestBody,
ConnectionsList,
Destination,
DestinationsList,
Source,
SourceDiscoverSchema,
SourcesList,
} from "./types";
import { getWorkspaceId, setWorkspaceId } from "./workspace";

const getApiUrl = (path: string): string => `http://localhost:8001/api/v1${path}`;

const apiRequest = <T = void>(
method: Cypress.HttpMethod,
path: string,
payload?: Cypress.RequestBody,
expectedStatus = 200
): Cypress.Chainable<T> =>
cy.request(method, getApiUrl(path), payload).then((response) => {
expect(response.status).to.eq(expectedStatus, "response status");
return response.body;
});

export const requestWorkspaceId = () =>
apiRequest<{ workspaces: Array<{ workspaceId: string }> }>("POST", "/workspaces/list").then(
({ workspaces: [{ workspaceId }] }) => {
setWorkspaceId(workspaceId);
}
);

export const requestConnectionsList = () =>
apiRequest<ConnectionsList>("POST", "/connections/list", { workspaceId: getWorkspaceId() });

export const requestCreateConnection = (body: ConnectionCreateRequestBody) =>
apiRequest<Connection>("POST", "/web_backend/connections/create", body);

export const requestUpdateConnection = (body: Record<string, unknown>) =>
apiRequest<Connection>("POST", "/web_backend/connections/update", body);

export const requestGetConnection = (body: ConnectionGetBody) =>
apiRequest<Connection>("POST", "/web_backend/connections/get", body);

export const requestDeleteConnection = (connectionId: string) =>
apiRequest("POST", "/connections/delete", { connectionId }, 204);

export const requestSourcesList = () =>
apiRequest<SourcesList>("POST", "/sources/list", { workspaceId: getWorkspaceId() });

export const requestSourceDiscoverSchema = (sourceId: string) =>
apiRequest<SourceDiscoverSchema>("POST", "/sources/discover_schema", { sourceId, disable_cache: true });

export const requestCreateSource = (body: Record<string, unknown>) =>
apiRequest<Source>("POST", "/sources/create", body);

export const requestDeleteSource = (sourceId: string) => apiRequest("POST", "/sources/delete", { sourceId }, 204);

export const requestDestinationsList = () =>
apiRequest<DestinationsList>("POST", "/destinations/list", { workspaceId: getWorkspaceId() });

export const requestCreateDestination = (body: Record<string, unknown>) =>
apiRequest<Destination>("POST", "/destinations/create", body);

export const requestDeleteDestination = (destinationId: string) =>
apiRequest("POST", "/destinations/delete", { destinationId }, 204);
2 changes: 2 additions & 0 deletions airbyte-webapp-e2e-tests/cypress/commands/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./api";
export * from "./payloads";
66 changes: 66 additions & 0 deletions airbyte-webapp-e2e-tests/cypress/commands/api/payloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ConnectionCreateRequestBody } from "./types";
import { getWorkspaceId } from "./workspace";

type RequiredConnectionCreateRequestProps = "name" | "sourceId" | "destinationId" | "syncCatalog" | "sourceCatalogId";
type CreationConnectRequestParams = Pick<ConnectionCreateRequestBody, RequiredConnectionCreateRequestProps> &
Partial<Omit<ConnectionCreateRequestBody, RequiredConnectionCreateRequestProps>>;

export const getConnectionCreateRequest = (params: CreationConnectRequestParams): ConnectionCreateRequestBody => ({
geography: "auto",
namespaceDefinition: "source",
namespaceFormat: "${SOURCE_NAMESPACE}",
nonBreakingChangesPreference: "ignore",
operations: [],
prefix: "",
scheduleType: "manual",
status: "active",
...params,
});

export const getPostgresCreateSourceBody = (name: string) => ({
name,
sourceDefinitionId: "decd338e-5647-4c0b-adf4-da0e75f5a750",
workspaceId: getWorkspaceId(),
connectionConfiguration: {
ssl_mode: { mode: "disable" },
tunnel_method: { tunnel_method: "NO_TUNNEL" },
replication_method: { method: "Standard" },
ssl: false,
port: 5433,
schemas: ["public"],
host: "localhost",
database: "airbyte_ci_source",
username: "postgres",
password: "secret_password",
},
});

export const getE2ETestingCreateDestinationBody = (name: string) => ({
name,
workspaceId: getWorkspaceId(),
destinationDefinitionId: "2eb65e87-983a-4fd7-b3e3-9d9dc6eb8537",
connectionConfiguration: {
type: "LOGGING",
logging_config: {
logging_type: "FirstN",
max_entry_count: 100,
},
},
});

export const getPostgresCreateDestinationBody = (name: string) => ({
name,
workspaceId: getWorkspaceId(),
destinationDefinitionId: "25c5221d-dce2-4163-ade9-739ef790f503",
connectionConfiguration: {
ssl_mode: { mode: "disable" },
tunnel_method: { tunnel_method: "NO_TUNNEL" },
ssl: false,
port: 5434,
schema: "public",
host: "localhost",
database: "airbyte_ci_destination",
username: "postgres",
password: "secret_password",
},
});
75 changes: 75 additions & 0 deletions airbyte-webapp-e2e-tests/cypress/commands/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
export interface Connection {
connectionId: string;
destination: Destination;
destinationId: string;
isSyncing: boolean;
name: string;
scheduleType: string;
schemaChange: string;
source: Source;
sourceId: string;
status: "active" | "inactive" | "deprecated";
nonBreakingChangesPreference: "ignore" | "disable";
syncCatalog: SyncCatalog;
}

export interface ConnectionCreateRequestBody {
destinationId: string;
geography: string;
name: string;
namespaceDefinition: string;
namespaceFormat: string;
nonBreakingChangesPreference: "ignore" | "disable";
operations: unknown[];
prefix: string;
scheduleType: string;
sourceCatalogId: string;
sourceId: string;
status: "active";
syncCatalog: SyncCatalog;
}

export interface ConnectionGetBody {
connectionId: string;
withRefreshedCatalog?: boolean;
}

export interface ConnectionsList {
connections: Connection[];
}

export interface Destination {
destinationDefinitionId: string;
destinationName: string;
destinationId: string;
connectionConfiguration: Record<string, unknown>;
}

export interface DestinationsList {
destinations: Destination[];
}

export interface Source {
sourceDefinitionId: string;
sourceName: string;
sourceId: string;
connectionConfiguration: Record<string, unknown>;
}

export interface SourceDiscoverSchema {
catalog: SyncCatalog;
catalogId: string;
}

export interface SourcesList {
sources: Source[];
}

export interface SyncCatalog {
streams: SyncCatalogStream[];
}

export interface SyncCatalogStream {
config: Record<string, unknown>;
stream: Record<string, unknown>;
}
9 changes: 9 additions & 0 deletions airbyte-webapp-e2e-tests/cypress/commands/api/workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let _workspaceId: string;

export const setWorkspaceId = (workspaceId: string) => {
_workspaceId = workspaceId;
};

export const getWorkspaceId = () => {
return _workspaceId;
};
89 changes: 62 additions & 27 deletions airbyte-webapp-e2e-tests/cypress/commands/db/queries.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,68 @@
export const createTable = (tableName: string, columns: string[]): string =>
`CREATE TABLE ${tableName}(${columns.join(", ")});`;

export const dropTable = (tableName: string) => `DROP TABLE IF EXISTS ${tableName}`;

export const alterTable = (tableName: string, params: { add?: string[]; drop?: string[] }): string => {
const adds = params.add ? params.add.map((add) => `ADD COLUMN ${add}`) : [];
const drops = params.drop ? params.drop.map((columnName) => `DROP COLUMN ${columnName}`) : [];
const alterations = [...adds, ...drops];

return `ALTER TABLE ${tableName} ${alterations.join(", ")};`;
};

export const insertIntoTable = (tableName: string, valuesByColumn: Record<string, unknown>): string => {
const keys = Object.keys(valuesByColumn);
const values = keys
.map((key) => valuesByColumn[key])
.map((value) => (typeof value === "string" ? `'${value}'` : value));

return `INSERT INTO ${tableName}(${keys.join(", ")}) VALUES(${values.join(", ")});`;
};

Comment on lines +1 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

export const insertMultipleIntoTable = (tableName: string, valuesByColumns: Array<Record<string, unknown>>): string =>
valuesByColumns.map((valuesByColumn) => insertIntoTable(tableName, valuesByColumn)).join("\n");

// Users table
export const createUsersTableQuery = `
CREATE TABLE users(id SERIAL PRIMARY KEY, col1 VARCHAR(200));`;
export const insertUsersTableQuery = `
INSERT INTO public.users(col1) VALUES('record1');
INSERT INTO public.users(col1) VALUES('record2');
INSERT INTO public.users(col1) VALUES('record3');`;
export const createUsersTableQuery = createTable("public.users", [
"id SERIAL",
"name VARCHAR(200) NULL",
"email VARCHAR(200) NULL",
"updated_at TIMESTAMP",
"CONSTRAINT users_pkey PRIMARY KEY (id)",
]);
export const insertUsersTableQuery = insertMultipleIntoTable("public.users", [
{ name: "Abigail", email: "[email protected]", updated_at: "2022-12-19 00:00:00" },
{ name: "Andrew", email: "[email protected]", updated_at: "2022-12-19 00:00:00" },
{ name: "Kat", email: "[email protected]", updated_at: "2022-12-19 00:00:00" },
]);

export const dropUsersTableQuery = `
DROP TABLE IF EXISTS users;`;
export const dropUsersTableQuery = dropTable("public.users");

// Cities table
export const createCitiesTableQuery = `
CREATE TABLE cities(city_code VARCHAR(8), city VARCHAR(200));`;

export const insertCitiesTableQuery = `
INSERT INTO public.cities(city_code, city) VALUES('BCN', 'Barcelona');
INSERT INTO public.cities(city_code, city) VALUES('MAD', 'Madrid');
INSERT INTO public.cities(city_code, city) VALUES('VAL', 'Valencia')`;

export const alterCitiesTableQuery = `
ALTER TABLE public.cities
DROP COLUMN "city_code",
ADD COLUMN "state" text,
ADD COLUMN "country" text;`;
export const dropCitiesTableQuery = `
DROP TABLE IF EXISTS cities;`;
export const createCitiesTableQuery = createTable("public.cities", ["city_code VARCHAR(8)", "city VARCHAR(200)"]);

export const insertCitiesTableQuery = insertMultipleIntoTable("public.cities", [
{
city_code: "BCN",
city: "Barcelona",
},
{ city_code: "MAD", city: "Madrid" },
{ city_code: "VAL", city: "Valencia" },
]);

export const alterCitiesTableQuery = alterTable("public.cities", {
add: ["state TEXT", "country TEXT"],
drop: ["city_code"],
});
export const dropCitiesTableQuery = dropTable("public.cities");

// Cars table
export const createCarsTableQuery = `
CREATE TABLE cars(id SERIAL PRIMARY KEY, mark VARCHAR(200), model VARCHAR(200), color VARCHAR(200));`;
export const dropCarsTableQuery = `
DROP TABLE IF EXISTS cars;`;
export const createCarsTableQuery = createTable("public.cars", [
"id SERIAL PRIMARY KEY",
"mark VARCHAR(200)",
"model VARCHAR(200)",
"color VARCHAR(200)",
]);

export const dropCarsTableQuery = dropTable("public.cars");
Loading