mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
add example of allure based integration testing structure
This commit is contained in:
1 parent
834aa3c8c2
commit
d3cad7bf46
15 files changed
+9368
-54
No files matched your search
+4
-4
@@ -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
|
||||
|
||||

|
||||
|
||||
- [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, позволяющий получать таблицы, добавлять роли, выполнять запросы и т.д.
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
api
|
||||
node_modules
|
||||
@@ -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
@@ -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`
|
||||
@@ -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 */
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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! */ })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum FEATURE {
|
||||
AUTHENTICATION = 'authentication',
|
||||
}
|
||||
@@ -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, () => { /* */ });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
Generated
+9022
File diff suppressed because it is too large.
Load diff
+11
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user