From afdf2dfd714a6a106e938f3765b48d2906bb531f Mon Sep 17 00:00:00 2001 From: egor-romanov Date: Wed, 13 Apr 2022 02:01:41 +0400 Subject: [PATCH] feat: add few classy tests on auth --- .gitignore | 2 + tests/.editorconfig | 14 - tests/.eslintignore | 2 - tests/.eslintrc.json | 30 -- tests/.jest/jest-custom-reporter.ts | 194 +++++++++ tests/.jest/jest-env.js | 8 + tests/.prettierignore | 4 + tests/.prettierrc | 7 + .../javascript/authentication.spec.ts | 373 ++++++++++++++++++ .../javascript/authentication.test.ts | 54 --- .../features/templates/authentication.test.ts | 35 -- tests/features/templates/authentication.ts | 35 ++ tests/jest-custom-reporter.ts | 119 ------ tests/jest-env.js | 5 - tests/jest.config.ts | 17 +- tests/package-lock.json | 172 +++++++- tests/package.json | 11 +- tests/tsconfig.json | 37 ++ 18 files changed, 835 insertions(+), 284 deletions(-) delete mode 100644 tests/.editorconfig delete mode 100644 tests/.eslintignore delete mode 100644 tests/.eslintrc.json create mode 100644 tests/.jest/jest-custom-reporter.ts create mode 100644 tests/.jest/jest-env.js create mode 100644 tests/.prettierignore create mode 100644 tests/.prettierrc create mode 100644 tests/features/javascript/authentication.spec.ts delete mode 100644 tests/features/javascript/authentication.test.ts delete mode 100644 tests/features/templates/authentication.test.ts create mode 100644 tests/features/templates/authentication.ts delete mode 100644 tests/jest-custom-reporter.ts delete mode 100644 tests/jest-env.js create mode 100644 tests/tsconfig.json diff --git a/.gitignore b/.gitignore index 6364efb6add..0eae2fb3788 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ out tmp coverage +allure-results +allure-report .nyc_output *.log diff --git a/tests/.editorconfig b/tests/.editorconfig deleted file mode 100644 index 0f3796685cd..00000000000 --- a/tests/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.js] -indent_size = 4 - -[*.json] -indent_size = 4 diff --git a/tests/.eslintignore b/tests/.eslintignore deleted file mode 100644 index 3b8cc4a015f..00000000000 --- a/tests/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -api -node_modules diff --git a/tests/.eslintrc.json b/tests/.eslintrc.json deleted file mode 100644 index 9073a834d07..00000000000 --- a/tests/.eslintrc.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": [ - "plugin:@typescript-eslint/recommended" - ], - "parserOptions": { - "ecmaVersion": 2018, - "sourceType": "module" - }, - "rules": { - "no-var": ["error"], - "curly": ["error", "all"], - "keyword-spacing": ["error", { "before": true, "after": true }], - "space-before-function-paren": ["error", { - "anonymous": "always", - "named": "never", - "asyncArrow": "always" - }], - "quotes": ["error", "single", { "avoidEscape": true }], - "brace-style": ["error", "1tbs", { "allowSingleLine": false }], - "linebreak-style": ["error", "unix"], - "strict": ["error", "global"], - "semi": ["error", "never"], - "indent": ["error", 4, {"SwitchCase": 1}], - "object-curly-spacing": ["error", "always"], - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-use-before-define": "off" - } -} diff --git a/tests/.jest/jest-custom-reporter.ts b/tests/.jest/jest-custom-reporter.ts new file mode 100644 index 00000000000..28b5a2bf095 --- /dev/null +++ b/tests/.jest/jest-custom-reporter.ts @@ -0,0 +1,194 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { AllureReporterApi, jasmine_, registerAllureReporter } from 'jest-allure2-adapter' +import { ContentType, Severity } from 'allure-js-commons' + +// eslint-disable-next-line @typescript-eslint/ban-types +type TestDecorator = ( + target: object, + property: string, + descriptor: PropertyDescriptor +) => PropertyDescriptor +export class JasmineAllureReporter implements jasmine_.CustomReporter { + allure: AllureReporterApi + + constructor(allure: AllureReporterApi) { + this.allure = allure + } + + suiteStarted(suite?: jasmine_.CustomReporterResult): void { + this.allure.startGroup(suite.description) + // some actions here on suite started + } + + suiteDone(): void { + // some actions here on suite end + this.allure.endGroup() + } + + specStarted(spec: jasmine_.CustomReporterResult): void { + this.allure.startTest(spec) + // some actions here on test started + } + + specDone(spec: jasmine_.CustomReporterResult): void { + // some actions here on spec end + this.allure.endTest(spec) + } +} + +registerAllureReporter(undefined, (allure) => new JasmineAllureReporter(allure)) + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace NodeJS { + interface Global { + reporter: AllureReporterApi + } + } +} + +function getAllure(): AllureReporterApi { + // @ts-ignore - we are checking if reporter is defined + if (!global.reporter) { + throw new Error('Unable to find Allure implementation') + } + // @ts-ignore - we know that reporter is an AllureReporterApi + return global.reporter +} + +export function step(nameFn: string | ((arg: T) => string)): TestDecorator { + return (target: object, propertyKey: string, descriptor: PropertyDescriptor) => { + const original: object = descriptor.value + let callable: (args: T) => void = () => { + /* */ + } + + if (typeof original === 'function') { + descriptor.value = function (...args: [T]) { + try { + const value: string = typeof nameFn === 'function' ? nameFn.apply(this, args) : nameFn + callable = () => getAllure().step(value, () => original.apply(this, args)) + // tslint:disable-next-line:no-console + console.info(`Step: ${value || nameFn}`) + } catch (e) { + // tslint:disable-next-line:no-console + console.error(`[ERROR] Failed to apply decorator: ${e}`) + } + return callable.apply(this, args) + } + } + return descriptor + } +} + +export function attachment(name: string, type: ContentType) { + return ( + _target: object, + _propertyKey: string, + descriptor: PropertyDescriptor + ): PropertyDescriptor => { + const original: object = descriptor.value + let callable: (args: T) => void = () => { + /* */ + } + + if (typeof original === 'function') { + descriptor.value = async function (...args: [T]) { + try { + const content: Buffer | string = await original.apply(this, args) + callable = () => + getAllure().step(name, () => { + getAllure().attachment(type.toString(), content, type) + }) + } catch (e) { + // tslint:disable-next-line:no-console + console.error(`[ERROR] Failed to apply decorator: ${e}`) + } + return callable.apply(this, args) + } + } + return descriptor + } +} + +export function attach(name: string, content: string | Buffer, type: ContentType): void { + getAllure().step(name, () => { + getAllure().attachment(type.toString(), content, type) + }) +} + +export function log(name: string, description?: string): void { + console.info(description ? `${name}: ${description}` : name) + getAllure().step(name, () => { + if (description) { + getAllure().step(description, () => { + /* */ + }) + } + }) +} + +export function feature(featureFn: string | ((arg: T) => string)): TestDecorator { + return processDecorator(featureFn, (name) => getAllure().feature(name)) +} + +export function story(storyFn: string | ((arg: T) => string)): TestDecorator { + return processDecorator(storyFn, (name) => getAllure().story(name)) +} + +export function severity( + severityFn: Severity | string | ((arg: T) => string | Severity) +): TestDecorator { + return processDecorator(severityFn, (name: Severity) => getAllure().severity(name)) +} + +export function tag(tagFn: string | ((arg: T) => string)): TestDecorator { + return processDecorator(tagFn, (name) => getAllure().tag(name)) +} + +export function owner(ownerFn: string | ((arg: T) => string)): TestDecorator { + return processDecorator(ownerFn, (name) => getAllure().owner(name)) +} + +export function description(descriptionFn: string | ((arg: T) => string)): TestDecorator { + return processDecorator(descriptionFn, (text) => getAllure().description(text)) +} + +function processDecorator( + parameterFn: string | ((arg: T) => string), + reporterFn: (arg: string) => void +): TestDecorator { + return (target: object, property: string, descriptor: PropertyDescriptor) => { + return processDescriptor(parameterFn, reporterFn, descriptor) + } +} + +function processDescriptor( + parameterFn: string | ((arg: T) => string), + reporterFn: (arg: string) => void, + descriptor: PropertyDescriptor +): PropertyDescriptor { + const original: object = descriptor.value + if (typeof original === 'function') { + descriptor.value = function (...args: [T]) { + try { + const value: string = + typeof parameterFn === 'function' ? parameterFn.apply(this, args) : parameterFn + reporterFn(value) + } catch (e) { + // tslint:disable-next-line:no-console + console.error(`[ERROR] Failed to apply decorator: ${e}`) + } + return original.apply(this, args) + } + } + + for (const prop of Object.keys(original)) { + if (original.hasOwnProperty(prop) && prop.startsWith('__testdeck_')) { + // @ts-ignore - we know that prop exists + descriptor.value[prop] = original[prop] + } + } + + return descriptor +} diff --git a/tests/.jest/jest-env.js b/tests/.jest/jest-env.js new file mode 100644 index 00000000000..2d372ef0c86 --- /dev/null +++ b/tests/.jest/jest-env.js @@ -0,0 +1,8 @@ +process.env.SUPABASE_DB_HOST = 'localhost' +process.env.SUPABASE_DB_PORT = '54322' +process.env.SUPABASE_GOTRUE = 'http://localhost:54321/auth/v1' +process.env.SUPABASE_URL = 'http://localhost:54321' +process.env.SUPABASE_KEY_ANON = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' +process.env.SUPABASE_KEY_ADMIN = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU' diff --git a/tests/.prettierignore b/tests/.prettierignore new file mode 100644 index 00000000000..915d0dfbc0f --- /dev/null +++ b/tests/.prettierignore @@ -0,0 +1,4 @@ +api +node_modules +package-lock.json +docker* diff --git a/tests/.prettierrc b/tests/.prettierrc new file mode 100644 index 00000000000..8df6df7757c --- /dev/null +++ b/tests/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/tests/features/javascript/authentication.spec.ts b/tests/features/javascript/authentication.spec.ts new file mode 100644 index 00000000000..e5548cf64ef --- /dev/null +++ b/tests/features/javascript/authentication.spec.ts @@ -0,0 +1,373 @@ +import { suite, test } from '@testdeck/jest' +import { faker } from '@faker-js/faker' +import { Severity } from 'allure-js-commons' +import postgres from 'postgres' +import crossFetch from 'cross-fetch' + +import { + ApiError, + createClient, + Session, + SupabaseClient, + SupabaseClientOptions, + User, +} from '@supabase/supabase-js' + +import { FEATURE } from '../templates/enums' +import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter' + +@suite('authentication') +class Authentication { + private static sql = postgres({ + host: process.env.SUPABASE_DB_HOST, + port: parseInt(process.env.SUPABASE_DB_PORT), + database: 'postgres', + username: 'postgres', + password: 'postgres', + }) + + @step('terminate sql connection') + static async after(): Promise { + try { + Authentication.sql.end({ timeout: 100 }) + return Promise.resolve(null) + } catch (err) { + return Promise.reject(err) + } + } + + @feature(FEATURE.AUTHENTICATION) + @severity(Severity.BLOCKER) + @description('When user sign up then corresponding user in auth schema should be created') + @test + async 'signup should create user'() { + const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) + + const fakeUser = { + email: faker.internet.exampleEmail(), + password: faker.internet.password(), + username: faker.internet.userName(), + } + const { user, session, error: signUpError } = await this.signUp(supabase, fakeUser) + + expect(user).toBeDefined() + expect(user.email).toEqual(fakeUser.email.toLowerCase()) + expect(session).toBeDefined() + expect(signUpError).toBeNull() + + const [createdUser] = await this.selectUser(user) + expect(createdUser.email).toEqual(fakeUser.email.toLowerCase()) + } + + @feature(FEATURE.AUTHENTICATION) + @severity(Severity.BLOCKER) + @description('When user sign up then he should not be logged in until he confirms his email') + @test + async 'new users'() { + // sign up user + const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) + + const fakeUser = { + email: faker.internet.exampleEmail(), + password: faker.internet.password(), + username: faker.internet.userName(), + } + const { user, error: signUpError } = await this.signUp(supabase, fakeUser) + + expect(user).toBeDefined() + expect(user.email).toEqual(fakeUser.email.toLowerCase()) + expect(signUpError).toBeNull() + + // check if user is not signed in cause he has not confirmed his email + const { error: errorInsertFailed } = await this.insertProfile(supabase, user, fakeUser) + log('check if insert failed caused user has not confirmed email') + expect(errorInsertFailed).not.toBeNull() + + // confirm user email and sign in + const [{ confirmation_token: token }] = await this.getConfirmationToken(user) + const verifyResponse = await this.verify(token) + expect(verifyResponse.ok).toBeTruthy() + + const { error: signInError } = await supabase.auth.signIn({ + email: fakeUser.email, + password: fakeUser.password, + }) + expect(signInError).toBeNull() + + // check if user is signed in + const { data: profile, error: errorInsert } = await this.insertProfile(supabase, user, fakeUser) + expect(errorInsert).toBeNull() + expect(profile.username).toMatch(fakeUser.username) + + const { data: profileGot } = await this.getUserProfile(supabase) + expect(profileGot.username).toMatch(profile.username) + + // check if user is able to sign out + const { error } = await this.signOut(supabase) + expect(error).toBeNull() + } + + @feature(FEATURE.AUTHENTICATION) + @severity(Severity.BLOCKER) + @description('When user sign up with phone then he should be logged in') + @test + async 'new users by phone'() { + // sign up user + const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) + + const fakeUser = { + password: faker.internet.password(), + username: faker.internet.userName(), + phone: faker.phone.phoneNumber('!#!##!######'), + } + const { user, session, error: signUpError } = await this.signUpByPhone(supabase, fakeUser) + + expect(user).toBeDefined() + expect(user.phone).toEqual(fakeUser.phone) + expect(session).toBeDefined() + expect(signUpError).toBeNull() + + // check if user is signed in + const { data: profile, error: errorInsert } = await this.insertProfile(supabase, user, fakeUser) + expect(errorInsert).toBeNull() + expect(profile.username).toMatch(fakeUser.username) + + const { data: profileGot } = await this.getUserProfile(supabase) + expect(profileGot.username).toMatch(profile.username) + + // check if user is able to sign out + const { error } = await this.signOut(supabase) + expect(error).toBeNull() + } + + @feature(FEATURE.AUTHENTICATION) + @severity(Severity.BLOCKER) + @description('When user is already signed up then he should be able to log in') + @test + async 'existing users'() { + // create user + const fakeUser = await this.createUser() + + // sign in as user + const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) + const { + session, + user, + error: signInError, + } = await supabase.auth.signIn({ + email: fakeUser.email, + password: fakeUser.password, + }) + + expect(user).toBeDefined() + expect(user.email).toEqual(fakeUser.email.toLowerCase()) + expect(session).toBeDefined() + expect(signInError).toBeNull() + + // check if user is signed in correctly and rls is working + const { data: profileInserted, error: errorInsert } = await this.insertProfile( + supabase, + user, + fakeUser + ) + expect(errorInsert).toBeNull() + expect(profileInserted.username).toMatch(fakeUser.username) + + const { data: profileGot } = await this.getUserProfile(supabase) + expect(profileGot.username).toMatch(profileInserted.username) + + // check if user is able to sign out + const { error } = await this.signOut(supabase) + expect(error).toBeNull() + } + + @feature(FEATURE.AUTHENTICATION) + @severity(Severity.NORMAL) + @description('When user is signed in then he should be able to get his info and metadata') + @test + async 'get user'() { + // create user + const username = faker.internet.userName() + const date = faker.date.recent().toUTCString() + const fakeUser = await this.createUser({ + username: username, + date: date, + }) + + // sign in as user + const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) + await supabase.auth.signIn({ + email: fakeUser.email, + password: fakeUser.password, + }) + + // get signed in user data + const user = this.getUser(supabase) + + log('Check if user is signed in correctly and can get his data') + expect(user).toBeDefined() + expect(user.email).toEqual(fakeUser.email.toLowerCase()) + expect(user.role).toEqual('authenticated') + expect(user.aud).toEqual('authenticated') + // verify if metadata is correctly set after sing up + expect(user.user_metadata).toEqual({ + username: username, + date: date, + }) + } + + // steps + + @step('Create Supabase client') + private createSupaClient( + url: string, + key: string, + options: SupabaseClientOptions = {} + ): SupabaseClient { + return createClient(url, key, options) + } + + @step('Create a valid user') + private async createUser(data: object = {}): Promise<{ + email: string + password: string + username: string + }> { + const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON) + + const fakeUser = { + email: faker.internet.exampleEmail(), + password: faker.internet.password(), + username: faker.internet.userName(), + } + const { user, error: signUpError } = await this.signUp(supabase, fakeUser, { + data: data, + }) + expect(signUpError).toBeNull() + + const [{ confirmation_token: token }] = await this.getConfirmationToken(user) + + await this.verify(token) + + return fakeUser + } + + @step((token: string) => `verify with token ${token}`) + private async verify(token: string): Promise { + return crossFetch(`${process.env.SUPABASE_GOTRUE}/verify`, { + method: 'POST', + body: JSON.stringify({ + type: 'signup', + token: token, + }), + }) + } + + @step((user: User) => `get confirmation token for user ${user.id}`) + private async getConfirmationToken(user: User): Promise<[{ confirmation_token: any }]> { + return Authentication.sql` + select confirmation_token + from auth.users + where id = ${user.id} + ` + } + + @step('I sign up with a valid email and password') + private async signUp( + supabase: SupabaseClient, + { + email = faker.internet.exampleEmail(), + password = faker.internet.password(), + }: { + email?: string + password?: string + } = {}, + options: { + redirectTo?: string + data?: object + } = {} + ): Promise<{ + user: User + session: Session + error: ApiError + }> { + return supabase.auth.signUp( + { + email: email, + password: password, + }, + options + ) + } + + @step('I sign up with a valid email and password') + private async signUpByPhone( + supabase: SupabaseClient, + { + phone = faker.phone.phoneNumber(), + password = faker.internet.password(), + }: { + phone?: string + password?: string + } = {}, + options: { + redirectTo?: string + data?: object + } = {} + ): Promise<{ + user: User + session: Session + error: ApiError + }> { + return supabase.auth.signUp( + { + phone: phone, + password: password, + }, + options + ) + } + + @step('Check if I am being able to log out') + private signOut(supabase: SupabaseClient): { error: any } | PromiseLike<{ error: any }> { + return supabase.auth.signOut() + } + + @step('Get user data, if there is a logged in user') + private getUser(supabase: SupabaseClient) { + return supabase.auth.user() + } + + @step('Check if I am logged in by checking if I can insert my profile') + private async insertProfile( + supabase: SupabaseClient, + user: User, + fakeUser: { + username: string + } + ): Promise<{ data: any; error: any }> { + return supabase + .from('profiles') + .insert({ + id: user.id, + username: fakeUser.username, + }) + .single() + } + + @step('Check if I am logged in by checking if I can get my profile') + private async getUserProfile(supabase: SupabaseClient): Promise<{ data: any }> { + return supabase.from('profiles').select().maybeSingle() + } + + @step((user: User) => `Get user by ID (${user.id}) from Supabase auth schema`) + private async selectUser(user: User): Promise<[{ email: string }]> { + return Authentication.sql` + select + email + from auth.users + where + id = ${user.id} + ` + } +} diff --git a/tests/features/javascript/authentication.test.ts b/tests/features/javascript/authentication.test.ts deleted file mode 100644 index fc5461f1199..00000000000 --- a/tests/features/javascript/authentication.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Severity } from 'allure-js-commons' -import { FEATURE } from '../templates/enums' - -describe('authentication', () => { - beforeEach(() => { - reporter - .description('Feature should work cool') - .feature(FEATURE.AUTHENTICATION) - reporter.severity(Severity.BLOCKER) - reporter.step('setup actions', () => { - /* implement it here */ - }) - }) - - test('New users', () => { - reporter.description('When user sign up then he should be logged in') - - reporter.step('Create Supabase anonymous client', () => { - /* implement it here */ - }) - - reporter.step('I sign up with a valid email and password', () => { - /* implement it here */ - }) - - reporter.step('Check if I am logged in', () => { - /* implement it here */ - }) - - reporter.step('Check if I am being able to log out', () => { - /* implement it here */ - }) - }) - - test('Existing users', () => { - reporter.description('When user is already signed up then he should be able to logged in') - - reporter.step('Create a valid user', () => { - /* implement it here */ - }) - - reporter.step('I sign in with a valid email and password', () => { - /* implement it here */ - }) - - reporter.step('Check if I am logged in', () => { - /* implement it here */ - }) - - reporter.step('Check if I am being able to log out', () => { - /* implement it here */ - }) - }) -}) diff --git a/tests/features/templates/authentication.test.ts b/tests/features/templates/authentication.test.ts deleted file mode 100644 index ee1aece3bba..00000000000 --- a/tests/features/templates/authentication.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Severity } from 'allure-js-commons' - -import { FEATURE } from './enums' - -describe('authentication', () => { - beforeEach(() => { - reporter - .feature(FEATURE.AUTHENTICATION) - reporter.severity(Severity.BLOCKER) - }) - - test('New users', () => { - reporter.description('When user sign up then he should be logged in') - - reporter.step('Create Supabase anonymous client', () => { /* don't modify! */ }) - - reporter.step('I sign up with a valid email and password', () => { /* don't modify! */ }) - - reporter.step('Check if I am logged in', () => { /* don't modify! */ }) - - reporter.step('Check if I am being able to log out', () => { /* don't modify! */ }) - }) - - test('Existing users', () => { - reporter.description('When user is already signed up then he should be able to logged in') - - reporter.step('Create a valid user', () => { /* don't modify! */ }) - - reporter.step('I sign in with a valid email and password', () => { /* don't modify! */ }) - - reporter.step('Check if I am logged in', () => { /* don't modify! */ }) - - reporter.step('Check if I am being able to log out', () => { /* don't modify! */ }) - }) -}) diff --git a/tests/features/templates/authentication.ts b/tests/features/templates/authentication.ts new file mode 100644 index 00000000000..9ef9872d0c4 --- /dev/null +++ b/tests/features/templates/authentication.ts @@ -0,0 +1,35 @@ +import { Severity } from 'allure-js-commons' + +import { FEATURE } from './enums' + +describe('authentication', () => { + beforeEach(() => { + reporter + .feature(FEATURE.AUTHENTICATION) + reporter.severity(Severity.BLOCKER) + }) + + test('New users', () => { + reporter.description('When user sign up then he should be logged in') + + reporter.step('Create Supabase anonymous client', () => { /* don't modify! */ }) + + reporter.step('I sign up with a valid email and password', () => { /* don't modify! */ }) + + reporter.step('Check if I am logged in', () => { /* don't modify! */ }) + + reporter.step('Check if I am being able to log out', () => { /* don't modify! */ }) + }) + + test('Existing users', () => { + reporter.description('When user is already signed up then he should be able to logged in') + + reporter.step('Create a valid user', () => { /* don't modify! */ }) + + reporter.step('I sign in with a valid email and password', () => { /* don't modify! */ }) + + reporter.step('Check if I am logged in', () => { /* don't modify! */ }) + + reporter.step('Check if I am being able to log out', () => { /* don't modify! */ }) + }) +}) diff --git a/tests/jest-custom-reporter.ts b/tests/jest-custom-reporter.ts deleted file mode 100644 index 4889fb3b1c1..00000000000 --- a/tests/jest-custom-reporter.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import { - AllureReporterApi, - jasmine_, - registerAllureReporter -} from 'jest-allure2-adapter'; -import { ContentType, Severity } from 'allure-js-commons'; - -// eslint-disable-next-line @typescript-eslint/ban-types -type TestDecorator = (target: object, property: string, descriptor: PropertyDescriptor) => PropertyDescriptor; -export class JasmineAllureReporter implements jasmine_.CustomReporter { - allure: AllureReporterApi; - - constructor(allure: AllureReporterApi) { - this.allure = allure; - } - - suiteStarted(suite?: jasmine_.CustomReporterResult): void { - this.allure.startGroup(suite.description); - // some actions here on suite started - } - - suiteDone(): void { - // some actions here on suite end - this.allure.endGroup(); - } - - specStarted(spec: jasmine_.CustomReporterResult): void { - this.allure.startTest(spec); - // some actions here on test started - } - - specDone(spec: jasmine_.CustomReporterResult): void { - // some actions here on spec end - this.allure.endTest(spec); - } -} - -registerAllureReporter( - undefined, - (allure) => new JasmineAllureReporter(allure) -); - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace NodeJS { - interface Global { - reporter: AllureReporterApi; - } - } -} - -function getAllure(): AllureReporterApi { - if (!global.reporter) { - throw new Error('Unable to find Allure implementation'); - } - return global.reporter; -} - -export function step(nameFn: string | ((arg: T) => string)): TestDecorator { - return (target: object, propertyKey: string, descriptor: PropertyDescriptor) => { - const original: object = descriptor.value; - let callable: (args: T) => void = () => {/* */}; - - if (typeof original === 'function') { - descriptor.value = function (...args: [T]) { - try { - const value: string = typeof nameFn === 'function' ? nameFn.apply(this, args) : nameFn; - callable = () => getAllure().step(value, () => original.apply(this, args)); - // tslint:disable-next-line:no-console - console.info(`Step: ${value || nameFn}`); - } catch (e) { - // tslint:disable-next-line:no-console - console.error(`[ERROR] Failed to apply decorator: ${e}`); - } - return callable.apply(this, args); - }; - } - return descriptor; - }; -} - -export function attachment(name: string, type: ContentType) { - return (_target: object, _propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor => { - const original: object = descriptor.value; - let callable: (args: T) => void = () => {/* */}; - - if (typeof original === 'function') { - descriptor.value = async function (...args: [T]) { - try { - const content: Buffer | string = await original.apply(this, args); - callable = () => getAllure().step(name, () => { - getAllure().attachment(type.toString(), content, type); - }); - } catch (e) { - // tslint:disable-next-line:no-console - console.error(`[ERROR] Failed to apply decorator: ${e}`); - } - return callable.apply(this, args); - }; - } - return descriptor; - }; -} - -export function attach(name: string, content: string | Buffer, type: ContentType): void { - getAllure().step(name, () => { - getAllure().attachment(type.toString(), content, type); - }); -} - -export function log(name: string, description?: string): void { - console.info(description ? `${name}: ${description}` : name); - getAllure().step(name, () => { - if (description) { - getAllure().step(description, () => { /* */ }); - } - }); -} \ No newline at end of file diff --git a/tests/jest-env.js b/tests/jest-env.js deleted file mode 100644 index 66cd730acd9..00000000000 --- a/tests/jest-env.js +++ /dev/null @@ -1,5 +0,0 @@ -process.env.SUPABASE_URL = 'http://localhost:8000' -process.env.SUPABASE_KEY_ANON = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE' -process.env.SUPABASE_KEY_ADMIN = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q' \ No newline at end of file diff --git a/tests/jest.config.ts b/tests/jest.config.ts index 3700b96a70a..3117c2e35cd 100644 --- a/tests/jest.config.ts +++ b/tests/jest.config.ts @@ -1,18 +1,17 @@ -import type { Config } from '@jest/types'; +import type { Config } from '@jest/types' const config: Config.InitialOptions = { preset: 'ts-jest', transform: { '^.+\\.ts?$': 'ts-jest', }, - moduleFileExtensions: ['ts', 'js', 'cjs', 'json', 'node',], + moduleFileExtensions: ['ts', 'js', 'cjs', 'json', 'node'], // globalSetup: './test/hooks.ts', // globalTeardown: './teardown.js', - setupFilesAfterEnv: [ - './jest-custom-reporter.ts', - ], - // setupFilesAfterEnv: ['jest-allure2-adapter/dist/setup-default',], - testTimeout: 25000, + setupFiles: ['/.jest/jest-env.js'], + setupFilesAfterEnv: ['/.jest/jest-custom-reporter.ts'], + testRunner: 'jest-jasmine2', + testTimeout: 5000, // testEnvironmentOptions: { // jiraUrl: "" // }, @@ -24,5 +23,5 @@ const config: Config.InitialOptions = { statements: 0, }, }, -}; -export default config; \ No newline at end of file +} +export default config diff --git a/tests/package-lock.json b/tests/package-lock.json index a7093872d95..a3ee3a3bdc6 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -9,15 +9,18 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { + "@faker-js/faker": "^6.1.2", + "@testdeck/jest": "^0.2.0", "@types/jest": "^27.4.0", "@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", "allure-commandline": "^2.17.2", "allure-js-commons": "^2.0.0-beta.14", + "cross-fetch": "^3.1.5", "eslint": "^8.7.0", - "faker": "^6.6.6", "jest": "^27.4.7", "jest-allure2-adapter": "^0.3.11", + "postgres": "^3.0.5", "ts-jest": "^27.1.3" } }, @@ -646,6 +649,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@faker-js/faker": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.1.2.tgz", + "integrity": "sha512-QSvmexHCxeRUk1/yKmoEDaWB5Hohjvtim5g2JJwy8S/l0L4b3y/GxSpE6vN4SBoVGGahEQW21uqyRr7EofI35A==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", @@ -984,6 +997,21 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@testdeck/core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.0.tgz", + "integrity": "sha512-2BAzKS18I+nFUb9kH2zK7Bs1CBD0WqaYikaLm+gRaOiQKYa8flvVzqlUGwjyOMmD16JFksgxYml4/7xm4tfEYA==", + "dev": true + }, + "node_modules/@testdeck/jest": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@testdeck/jest/-/jest-0.2.0.tgz", + "integrity": "sha512-YS8RupTg1Sb5TLkRGALppsI0iX8/LMDieRvPN0WGuJ+22zDugMXBBzN43X60pO+9HQ343qLUNzoV7RfQHHTQ+A==", + "dev": true, + "dependencies": { + "@testdeck/core": "^0.2.0" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1841,6 +1869,15 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2420,12 +2457,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/faker": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz", - "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==", - "dev": true - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3985,6 +4016,48 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4200,6 +4273,16 @@ "node": ">=8" } }, + "node_modules/postgres": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.0.5.tgz", + "integrity": "sha512-/9VVVRG+bzLc/AB6QukEV+SERra2IRduGd8PDz/ZGu+1PQ6+TgCQYvX87GtPfxcYW4XZ58cyurJojnphdazW1g==", + "dev": true, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -5621,6 +5704,12 @@ } } }, + "@faker-js/faker": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.1.2.tgz", + "integrity": "sha512-QSvmexHCxeRUk1/yKmoEDaWB5Hohjvtim5g2JJwy8S/l0L4b3y/GxSpE6vN4SBoVGGahEQW21uqyRr7EofI35A==", + "dev": true + }, "@humanwhocodes/config-array": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", @@ -5892,6 +5981,21 @@ "@sinonjs/commons": "^1.7.0" } }, + "@testdeck/core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.0.tgz", + "integrity": "sha512-2BAzKS18I+nFUb9kH2zK7Bs1CBD0WqaYikaLm+gRaOiQKYa8flvVzqlUGwjyOMmD16JFksgxYml4/7xm4tfEYA==", + "dev": true + }, + "@testdeck/jest": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@testdeck/jest/-/jest-0.2.0.tgz", + "integrity": "sha512-YS8RupTg1Sb5TLkRGALppsI0iX8/LMDieRvPN0WGuJ+22zDugMXBBzN43X60pO+9HQ343qLUNzoV7RfQHHTQ+A==", + "dev": true, + "requires": { + "@testdeck/core": "^0.2.0" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -6536,6 +6640,15 @@ "safe-buffer": "~5.1.1" } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6963,12 +7076,6 @@ "jest-message-util": "^27.4.6" } }, - "faker": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz", - "integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==", - "dev": true - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8180,6 +8287,39 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -8341,6 +8481,12 @@ "find-up": "^4.0.0" } }, + "postgres": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.0.5.tgz", + "integrity": "sha512-/9VVVRG+bzLc/AB6QukEV+SERra2IRduGd8PDz/ZGu+1PQ6+TgCQYvX87GtPfxcYW4XZ58cyurJojnphdazW1g==", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", diff --git a/tests/package.json b/tests/package.json index 88988be3fcd..56cbbeb5021 100644 --- a/tests/package.json +++ b/tests/package.json @@ -3,21 +3,26 @@ "version": "1.0.0", "description": "These tests can be run with Docker.", "scripts": { - "test:js": "node ./node_modules/.bin/cucumber-js tests/ -r tests/features/definitions" + "test": "jest", + "allure:generate": "rm -rf allure-report && node_modules/allure-commandline/bin/allure generate", + "allure:serve": "node_modules/allure-commandline/bin/allure serve", + "test:report": "npm run allure:generate && npm run allure:serve" }, "author": "Supabase, Inc.", "license": "MIT", - "dependencies": {}, "devDependencies": { + "@faker-js/faker": "^6.1.2", + "@testdeck/jest": "^0.2.0", "@types/jest": "^27.4.0", "@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", "allure-commandline": "^2.17.2", "allure-js-commons": "^2.0.0-beta.14", + "cross-fetch": "^3.1.5", "eslint": "^8.7.0", - "faker": "^6.6.6", "jest": "^27.4.7", "jest-allure2-adapter": "^0.3.11", + "postgres": "^3.0.5", "ts-jest": "^27.1.3" } } diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 00000000000..e099ff42ecd --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "esModuleInterop": true, + "inlineSources": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es6", + "DOM" + ], + "removeComments": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "sourceMap": true, + "declaration": true, + "baseUrl": ".", + "paths": { + "*": [ + "node_modules/*", + "./types/*" + ] + }, + "typeRoots": [ + "./node_modules/@types/", + "./node_modules/allure2-js-commons/dist/declarations/**/" + ] + }, + "exclude": [ + "node_modules" + ], + "compileOnSave": false +}