add example of allure based integration testing structure

This commit is contained in:
egor-romanov committed 2022-04-08 16:34:15 +04:00
1 parent 834aa3c8c2
commit d3cad7bf46
15 files changed
+9368 -54

No files matched your search

+4 -4
View File
@@ -6,7 +6,7 @@
# Supabase
[Supabase](https://supabase.com) – это альтернатива Firebase с открытым исходным кодом. Мы создаем функционал Firebase, используя инструменты Enterprise уровня с открытым исходным кодом.
[Supabase](https://supabase.com) – это альтернатива Firebase с открытым исходным кодом. Мы создаем функциональность Firebase, используя инструменты Enterprise уровня с открытым исходным кодом.
- [x] Облачная база данных Postgres
- [x] Подписки в режиме реального времени
@@ -41,7 +41,7 @@
## Как это работает
Supabase - это сочетание инструментов с открытым исходным кодом. Мы создаем функционал Firebase, используя инструменты Enterprise уровня с открытым исходным кодом. Если инструмент с открытой лицензией MIT, Apache 2 или аналогичной существует, мы будем использовать и поддерживать его. Если такого инструмента не существует, мы создаем и открываем его в публичный доступ сами. Supabase не является точной копией Firebase. Наша цель - предоставить разработчикам опыт работы с Firebase, используя инструменты с открытым исходным кодом.
Supabase - это сочетание инструментов с открытым исходным кодом. Мы создаем функциональность Firebase, используя инструменты Enterprise уровня с открытым исходным кодом. Если инструмент с открытой лицензией MIT, Apache 2 или аналогичной существует, мы будем использовать и поддерживать его. Если такого инструмента не существует, мы создаем и открываем его в публичный доступ сами. Supabase не является точной копией Firebase. Наша цель - предоставить разработчикам опыт работы с Firebase, используя инструменты с открытым исходным кодом.
**Текущая архитектура**
@@ -49,8 +49,8 @@ Supabase – это [облачная платформа](https://app.supabase.i
![Архитектура](https://supabase.com/docs/assets/images/supabase-architecture-9050a7317e9ec7efb7807f5194122e48.png)
- [PostgreSQL](https://www.postgresql.org/) – это объектно-реляционная система баз данных с более чем 30 годами активной разработки, которая завоевала прочную репутацию благодаря надежности, устойчивости функционала и производительности.
- [Realtime](https://github.com/supabase/realtime) – это сервер, написанный на Elixir, который позволяет cледить за вставками, обновлениями и удалениями в базе PostgreSQL с помощью веб-сокета. Supabase использует встроенный функционал репликации Postgres, преобразует поток байтов репликации в JSON, а затем передает JSON через веб-сокеты.
- [PostgreSQL](https://www.postgresql.org/) – это объектно-реляционная система баз данных с более чем 30 годами активной разработки, которая завоевала прочную репутацию благодаря надежности, устойчивости функциональности и производительности.
- [Realtime](https://github.com/supabase/realtime) – это сервер, написанный на Elixir, который позволяет cледить за вставками, обновлениями и удалениями в базе PostgreSQL с помощью веб-сокета. Supabase использует встроенную функциональность репликации Postgres, преобразует поток байтов репликации в JSON, а затем передает JSON через веб-сокеты.
- [PostgREST](http://postgrest.org/) – это веб-сервер, который превращает вашу базу данных PostgreSQL в RESTful API.
- [Storage](https://github.com/supabase/storage-api) предоставляет RESTful интерфейс для управления файлами, хранящимися в S3, используя Postgres для управления разрешениями.
- [postgres-meta](https://github.com/supabase/postgres-meta) – это RESTful API для управления Postgres, позволяющий получать таблицы, добавлять роли, выполнять запросы и т.д.
+14
View File
@@ -0,0 +1,14 @@
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
+2
View File
@@ -0,0 +1,2 @@
api
node_modules
+30
View File
@@ -0,0 +1,30 @@
{
"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"
}
}
+5 -1
View File
@@ -2,6 +2,10 @@
These tests can be run with Docker.
We can use either [allure-commandline](https://github.com/allure-framework/allure2) as it is open source and <https://github.com/marketplace/actions/allure-report-with-history> as an github action to host reports with history on GitHub Pages.
Or use their paid version if we want a bit more groove and possibility to manage test cases not only as code but also manually: <https://qameta.io>. It can be self-hosted or cloud. Not sure that we need it now, but we can always migrate seamlessly to it if we would need it.
### Steps
In the parent folder:
@@ -11,4 +15,4 @@ In the parent folder:
### Clean up
- `npm run docker:remove`
- `npm run docker:remove`
-13
View File
@@ -1,13 +0,0 @@
Feature: User Authentication
Scenario: New users
Given an anonymous user
When I sign up with a valid email and password
Then I should be logged in
And I should be able to log out
Scenario: Existing users
Given an existing user
When I sign up with a valid email and password
Then I should be logged in
And I should be able to log out
@@ -1,25 +0,0 @@
const { Given, Then, When } = require('cucumber')
Given('an anonymous user', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
Given('an existing user', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
When('I sign up with a valid email and password', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
Then('I should be logged in', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
Then('I should be able to log out', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending'
})
@@ -0,0 +1,54 @@
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 */
})
})
})
+41 -10
View File
@@ -1,13 +1,44 @@
from behave import *
import allure
@given('an anonymous user')
def step_impl(context):
pass
@allure.severity(allure.severity_level.BLOCKER)
@allure.suite("authentication")
@allure.feature("authentication")
class TestAuthentication(object):
@allure.description("""When user sign up then he should be logged in""")
def test_new_users(self):
self.create_supabase_anonymous_client()
self.sign_up_valid("email", "password")
self.check_logged_in()
self.check_log_out()
@when('an existing user')
def step_impl(context):
assert True is not False
def test_existing_users(self):
""" When user is already signed up then he should be able to logged in """
self.create_valid_user()
self.sign_in_valid("email", "password")
self.check_logged_in()
self.check_log_out()
@allure.step("Create Supabase anonymous client")
def create_supabase_anonymous_client(self):
pass
@allure.step("I sign up with a valid {email} and {password}")
def sign_up_valid(self, email, password="password"):
pass
@allure.step("Create a valid user")
def create_valid_user(self):
pass
@allure.step("I sign in with a valid {email} and {password}")
def sign_in_valid(self, email, password="password"):
pass
@allure.step("Check if I am logged in")
def check_logged_in(self):
pass
@allure.step("Check if I am being able to log out")
def check_log_out(self):
pass
@then('I sign up with a valid email and password')
def step_impl(context):
assert context.failed is False
@@ -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! */ })
})
})
+3
View File
@@ -0,0 +1,3 @@
export enum FEATURE {
AUTHENTICATION = 'authentication',
}
+119
View File
@@ -0,0 +1,119 @@
/* 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<T>(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<T>(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, () => { /* */ });
}
});
}
+28
View File
@@ -0,0 +1,28 @@
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
preset: 'ts-jest',
transform: {
'^.+\\.ts?$': 'ts-jest',
},
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,
// testEnvironmentOptions: {
// jiraUrl: ""
// },
coverageThreshold: {
global: {
branches: 0,
functions: 0,
lines: 0,
statements: 0,
},
},
};
export default config;
+9022
View File
File diff suppressed because it is too large. Load diff
+11 -1
View File
@@ -7,7 +7,17 @@
},
"author": "Supabase, Inc.",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"cucumber": "^7.0.0-rc.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",
"eslint": "^8.7.0",
"faker": "^6.6.6",
"jest": "^27.4.7",
"jest-allure2-adapter": "^0.3.11",
"ts-jest": "^27.1.3"
}
}