chore: Delete the tests package (#32527)

* Remove the tests package.

* Remove all workspace configs for the tests package.
This commit is contained in:
Ivan Vasilov
2025-01-08 21:29:45 +01:00
committed by GitHub
parent 900b4bbad6
commit 11caf61f43
27 changed files with 255 additions and 2927 deletions

View File

@@ -22,10 +22,6 @@
"typecheck": "turbo --continue typecheck",
"test:prettier": "prettier -c '{apps,packages}/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'",
"format": "prettier --write '{apps,packages}/**/*.{js,jsx,ts,tsx,css,md,mdx,json}'",
"docker:dev": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes",
"docker:up": "cd docker && docker compose up",
"docker:down": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans",
"docker:remove": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs",
"test:docs": "turbo run test --filter=docs",
"test:ui": "turbo run test --filter=ui",
"test:ui-patterns": "turbo run test --filter=ui-patterns",

720
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
packages:
- "apps/*"
- "packages/*"
- "tests"
- "playwright-tests"
catalog:

View File

@@ -1,194 +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 {
// @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<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, () => {
/* */
})
}
})
}
export function feature<T>(featureFn: string | ((arg: T) => string)): TestDecorator {
return processDecorator(featureFn, (name) => getAllure().feature(name))
}
export function story<T>(storyFn: string | ((arg: T) => string)): TestDecorator {
return processDecorator(storyFn, (name) => getAllure().story(name))
}
export function severity<T>(
severityFn: Severity | string | ((arg: T) => string | Severity)
): TestDecorator {
return processDecorator(severityFn, (name: Severity) => getAllure().severity(name))
}
export function tag<T>(tagFn: string | ((arg: T) => string)): TestDecorator {
return processDecorator(tagFn, (name) => getAllure().tag(name))
}
export function owner<T>(ownerFn: string | ((arg: T) => string)): TestDecorator {
return processDecorator(ownerFn, (name) => getAllure().owner(name))
}
export function description<T>(descriptionFn: string | ((arg: T) => string)): TestDecorator {
return processDecorator(descriptionFn, (text) => getAllure().description(text))
}
function processDecorator<T>(
parameterFn: string | ((arg: T) => string),
reporterFn: (arg: string) => void
): TestDecorator {
return (target: object, property: string, descriptor: PropertyDescriptor) => {
return processDescriptor(parameterFn, reporterFn, descriptor)
}
}
function processDescriptor<T>(
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
}

View File

@@ -1,14 +0,0 @@
export default () => {
process.env.SUPABASE_DB_HOST = process.env.SUPABASE_DB_HOST ?? 'localhost'
process.env.SUPABASE_DB_PORT = process.env.SUPABASE_DB_PORT ?? '5432'
process.env.SUPABASE_DB_PASS =
process.env.SUPABASE_DB_PASS ?? 'your-super-secret-and-long-postgres-password'
process.env.SUPABASE_GOTRUE = process.env.SUPABASE_GOTRUE ?? 'http://localhost:8000/auth/v1'
process.env.SUPABASE_URL = process.env.SUPABASE_URL ?? 'http://localhost:8000'
process.env.SUPABASE_KEY_ANON =
process.env.SUPABASE_KEY_ANON ??
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE'
process.env.SUPABASE_KEY_ADMIN =
process.env.SUPABASE_KEY_ADMIN ??
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q'
}

View File

@@ -1,25 +0,0 @@
import { createClient, User, UserResponse } from '@supabase/supabase-js'
const removeAllUsers = async () => {
const sb = createClient(
process.env.SUPABASE_URL as string,
process.env.SUPABASE_KEY_ADMIN as string
)
const {
data: { users },
} = await sb.auth.admin.listUsers()
const promises: Promise<UserResponse>[] = []
users.map((u) => {
sb.from('profiles')
.delete()
.match({ id: u.id })
.then(() => promises.push(sb.auth.admin.deleteUser(u.id)))
})
await Promise.all(promises)
}
export default async () => {
await removeAllUsers()
}

View File

@@ -1,4 +0,0 @@
api
node_modules
package-lock.json
docker*

View File

@@ -1,7 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 100
}

View File

@@ -1,18 +0,0 @@
## Tests
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:
- `npm run docker:dev`
- `npm run test`
### Clean up
- `npm run docker:remove`

View File

@@ -1,12 +0,0 @@
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
console.log('Hello from Functions!')
serve(async (req) => {
const { name } = await req.json()
const data = {
message: `Hello ${name}!`,
}
return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })
})

View File

@@ -1,319 +0,0 @@
import { params, retries, suite, test } from '@testdeck/jest'
import { faker } from '@faker-js/faker'
import { Severity } from 'allure-js-commons'
import { AdminUserAttributes, AuthError, SupabaseClient, UserResponse } from '@supabase/supabase-js'
import { FEATURE } from '../templates/enums'
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
@suite('auth_admin')
class AuthenticationAPI extends Hooks {
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you create user then it has to be in auth db schema')
@test
async 'create user via admin api'() {
const { user, error } = await this.createUserAsAdmin()
expect(error).toBeNull()
expect(user).not.toBeNull()
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you create user then he can sign in')
@test
async 'user created by admin can login'() {
const { user, error } = await this.createUserAsAdmin()
expect(error).toBeNull()
expect(user).not.toBeNull()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const {
data: { user: createdUser },
error: getErr,
} = await supabase.auth.signInWithPassword({
email: user.email,
password: user.password,
})
expect(getErr).toBeNull()
expect(createdUser).not.toBeNull()
expect(createdUser.id).toBe(user.id)
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.CRITICAL)
@description('When you try to create user with anon key then you should get error')
@test
async 'admin create user with anon key should fail'() {
const fakeUser = {
email: faker.internet.exampleEmail(),
password: faker.internet.password(),
email_confirm: true,
}
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const {
error,
data: { user },
} = await supabase.auth.admin.createUser(fakeUser)
expect(user).toBeNull()
expect(error).not.toBeNull()
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.CRITICAL)
@description('When you try to create user as logged in user then you should get error')
@test
async 'admin create user with logged in user should fail'() {
const { user } = await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const { error: signInError } = await supabase.auth.signInWithPassword({
email: user.email,
password: user.password,
})
expect(signInError).toBeNull()
const fakeUser = {
email: faker.internet.exampleEmail(),
password: faker.internet.password(),
}
const {
error,
data: { user: newUser },
} = await supabase.auth.admin.createUser(fakeUser)
expect(newUser).toBeNull()
expect(error).not.toBeNull()
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you list users then you should get all users')
@test
async 'list users with service key'() {
const { user: user1 } = await this.createUserAsAdmin()
const { user: user2 } = await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const {
data: { users },
error,
} = await supabase.auth.admin.listUsers()
expect(error).toBeNull()
expect(users).not.toBeNull()
expect(users.length).toBeGreaterThanOrEqual(2)
expect(users.map((u) => u.id)).toEqual(expect.arrayContaining([user1.id, user2.id]))
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.CRITICAL)
@description('When you try to list user with anon key then you should get error')
@test
async 'list users with anon key should fail'() {
await this.createUserAsAdmin()
await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const {
data: { users },
error,
} = await supabase.auth.admin.listUsers()
expect(error).not.toBeNull()
expect(users).toHaveLength(0)
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.CRITICAL)
@description('When you try to list user as logged in user then you should get error')
@test
async 'list users as logged in user should fail'() {
const { user } = await this.createUserAsAdmin()
await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const { error: signInError } = await supabase.auth.signInWithPassword({
email: user.email,
password: user.password,
})
expect(signInError).toBeNull()
const {
data: { users },
error,
} = await supabase.auth.admin.listUsers()
expect(error).not.toBeNull()
expect(users).toHaveLength(0)
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you get provider url then corresponding auth provider url should be returned')
@params.skip({ provider: 'google', options: {}, expectedURL: 'todo' })
@params.skip({
provider: 'google',
options: { redirectTo: 'todo', scopes: 'todo' },
expectedURL: 'todo',
})
@params.skip({ provider: 'twitter', options: {}, expectedURL: 'todo' })
// ...
async 'get url for provider'() {
// todo
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you get user by id he has to be returned')
@test
async 'get user should work'() {
const { user } = await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const {
data: { user: foundUser },
error,
} = await supabase.auth.admin.getUserById(user.id)
expect(error).toBeNull()
expect(foundUser).not.toBeNull()
expect(foundUser.id).toBe(user.id)
expect(foundUser.email).toBe(user.email)
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you update user then this user has to be updated')
@test
async 'update user should work'() {
const { user } = await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const updatedUser = {
email: faker.internet.exampleEmail(),
phone: faker.phone.phoneNumber('!#!##!######'),
}
let {
data: { user: resultUser },
error,
} = await this.updateWithRetries(supabase, user.id, updatedUser)
expect(error).toBeNull()
expect(resultUser).not.toBeNull()
expect(resultUser.id).toBe(user.id)
expect(resultUser.email).toBe(updatedUser.email)
expect(resultUser.phone).toBe(updatedUser.phone)
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.NORMAL)
@description('When you delete user then this user has to be removed')
@test
async 'delete user should work'() {
const { user } = await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const {
data: { user: deletedUser },
error,
} = await supabase.auth.admin.deleteUser(user.id)
expect(error).toBeNull()
const {
data: { user: foundUser },
error: getError,
} = await supabase.auth.admin.getUserById(user.id)
expect(getError).not.toBeNull()
expect(foundUser).toBeNull()
}
@feature(FEATURE.AUTH_ADMIN)
@severity(Severity.CRITICAL)
@description('When you delete user with anon key you have to receive an error')
@test
async 'delete user with anon key should fail'() {
const { user } = await this.createUserAsAdmin()
const { user: villain } = await this.createUserAsAdmin()
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
await supabase.auth.signInWithPassword({
email: villain.email,
password: villain.password,
})
const {
data: { user: deletedUser },
error,
} = await supabase.auth.admin.deleteUser(user.id)
expect(error).not.toBeNull()
expect(deletedUser).toBeNull()
const sbAdmin = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const {
data: { user: foundUser },
error: getError,
} = await sbAdmin.auth.admin.getUserById(user.id)
expect(getError).toBeNull()
expect(foundUser).not.toBeNull()
expect(foundUser.email).toBe(user.email)
}
@step('Create a user as admin')
async createUserAsAdmin(data: AdminUserAttributes = undefined): Promise<{
user: {
email: string
password: string
username: string
id: string
}
error: AuthError
}> {
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
let fakeUser: AdminUserAttributes
if (data) {
fakeUser = data
} else {
fakeUser = {
email: faker.internet.exampleEmail(),
password: faker.internet.password(),
email_confirm: true,
}
}
const {
error,
data: { user },
} = await supabase.auth.admin.createUser(fakeUser)
return {
error: error,
user: {
email: user?.email,
password: fakeUser.password,
username: faker.internet.userName(),
id: user?.id,
},
}
}
@step('Update user with retries')
async updateWithRetries(supabase: SupabaseClient, uid: string, attributes: AdminUserAttributes) {
let result: UserResponse
for (let i = 1; i < 5; i++) {
result = await supabase.auth.admin.updateUserById(uid, attributes)
if (result.error && result.error.name === 'AuthRetryableFetchError') {
await new Promise((resolve) => setTimeout(resolve, 1000 * i))
} else {
break
}
}
return result
}
}

View File

@@ -1,330 +0,0 @@
import { suite, test, timeout } from '@testdeck/jest'
import { faker } from '@faker-js/faker'
import { Severity } from 'allure-js-commons'
import { AuthChangeEvent, Session } from '@supabase/supabase-js'
import { FEATURE } from '../templates/enums'
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
@suite('authentication')
class Authentication extends Hooks {
@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 {
data: { user, session },
error: signUpError,
} = await this.signUp(supabase, fakeUser)
expect(signUpError).toBeNull()
expect(user).toBeDefined()
expect(user.email).toEqual(fakeUser.email.toLowerCase())
expect(session).toBeDefined()
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 'sing up new user and sign in'() {
// 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 {
data: { user, session: emptySession },
error: signUpError,
} = await this.signUp(supabase, fakeUser)
expect(signUpError).toBeNull()
expect(user).toBeDefined()
expect(user.email).toEqual(fakeUser.email.toLowerCase())
expect(emptySession).not.toBeNull()
const {
data: { session },
error: signInError,
} = await supabase.auth.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
expect(signInError).toBeNull()
expect(session).toBeDefined()
await supabase.auth.setSession(session)
// 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 'create new users by phone auth'() {
// 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 {
data: { user, session },
error: signUpError,
} = await this.signUpByPhone(supabase, fakeUser)
expect(signUpError).toBeNull()
expect(user).toBeDefined()
expect(user.phone).toEqual(fakeUser.phone)
expect(session).toBeDefined()
// 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 should be able to login'() {
// 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 {
data: { session, user },
error: signInError,
} = await supabase.auth.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
expect(signInError).toBeNull()
expect(session).toBeDefined()
expect(user).toBeDefined()
expect(user.email).toEqual(fakeUser.email.toLowerCase())
// 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 should return logged in 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.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
// get signed in user data
const {
data: { user },
error: getUserErr,
} = await this.getUser(supabase)
log('Check if user is signed in correctly and can get his data')
expect(getUserErr).toBeNull()
expect(user).not.toBeNull()
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,
})
}
@feature(FEATURE.AUTHENTICATION)
@severity(Severity.NORMAL)
@description('When user is signed in then he should be able update himself in auth schema')
@test.skip
async 'update user should update logged in user'() {
// create user
const fakeUser = await this.createUser()
// sign in as user
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
await supabase.auth.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
// get signed in user data
const user = await this.getUser(supabase)
// update user
const updParams = {
email: faker.internet.email(),
password: faker.internet.password(),
phone: faker.phone.phoneNumber('!#!##!######'),
}
const {
data: { user: updUser },
error: updUserError,
} = await this.updateUser(supabase, updParams)
expect(updUserError).toBeNull()
expect(updUser).not.toBeNull()
expect(updUser.email).toEqual(updParams.email.toLowerCase())
expect(updUser.phone).toEqual(updParams.phone)
// get user and check it was updated
const updatedUser = await this.getUser(supabase)
expect(updatedUser.data.user.email).toEqual(updParams.email.toLowerCase())
expect(updatedUser.data.user.phone).toEqual(updParams.phone)
// sign in with new credentials
await supabase.auth.signOut()
const signIn = await supabase.auth.signInWithPassword({
email: updParams.email,
password: updParams.password,
})
expect(signIn.error).toBeNull()
}
@feature(FEATURE.AUTHENTICATION)
@severity(Severity.NORMAL)
@description('When user changes session then he still should be correctly logined')
@test
async 'set session should set new auth'() {
// create user
const fakeUser = await this.createUser()
// sign in as user
const sb = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const {
data: { session },
} = await sb.auth.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const { error: sessionErr } = await supabase.auth.setSession(session)
expect(sessionErr).toBeNull()
// check if user is signed in correctly and rls is working
const { data: profileInserted, error: errorInsert } = await this.insertProfile(
supabase,
fakeUser,
fakeUser
)
expect(errorInsert).toBeNull()
expect(profileInserted).toHaveLength(1)
expect(profileInserted[0].username).toMatch(fakeUser.username)
}
@feature(FEATURE.AUTHENTICATION)
@severity(Severity.NORMAL)
@description('When user subscribes on auth changes then user has to receive auth updates')
@test
async 'on auth state changed should return events'() {
// create user
const fakeUser = await this.createUser()
const events: { event: AuthChangeEvent; token: string }[] = []
const onAuthStateChanged = (event: AuthChangeEvent, session: Session) => {
log('onAuthStateChanged triggered', event)
events.push({ event, token: session?.access_token })
}
// create client and subscribe on auth state changes
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(onAuthStateChanged)
// sign in as user
await supabase.auth.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
// update user
const updParams = {
email: faker.internet.email(),
password: faker.internet.password(),
phone: faker.phone.phoneNumber('!#!##!######'),
}
const { error: updUserError } = await this.updateUser(supabase, updParams)
expect(updUserError).toBeNull()
// remove subscription and sign out
subscription.unsubscribe()
await supabase.auth.signOut()
// check if sign in and update events were triggered and sign out event was not triggered
expect(events).toHaveLength(2)
expect(events.map((e) => e.event)).toEqual(
expect.arrayContaining(['SIGNED_IN', 'USER_UPDATED'])
)
expect(events.map((e) => e.event)).not.toContain('SIGNED_OUT')
}
}

View File

@@ -1,42 +0,0 @@
import { params, suite, test } from '@testdeck/jest'
import { faker } from '@faker-js/faker'
import { Severity } from 'allure-js-commons'
import { exec, ExecException } from 'child_process'
import os from 'os'
import { Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js'
import { FEATURE } from '../templates/enums'
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
import path from 'path'
@suite('functions')
class Functions extends Hooks {
@feature(FEATURE.STORAGE)
@severity(Severity.NORMAL)
@description('When you get functions client then you are able to set auth')
@test.skip
async 'set auth'() {
// execute cli command
const deno = path.join(os.homedir(), '.supabase', 'deno')
const funcPath = path.join(process.cwd(), 'data', 'func.ts')
let prom = new Promise<{ error: ExecException; stdout: string; stderr: string }>((resolve) => {
exec(`${deno} bundle --no-check=remote --quiet ${funcPath}`, (error, stdout, stderr) =>
resolve({ error, stdout, stderr })
)
})
const { supabase } = await this.createSignedInSupaClient()
const sb = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY)
const {
data: { session },
} = await supabase.auth.getSession()
sb.functions.setAuth(session.access_token)
const { error, stdout, stderr } = await prom
expect([null, undefined]).toContain(error)
expect([null, undefined]).toContain(stderr)
expect(stdout).toBeDefined()
sb.functions.invoke('get_user')
}
}

View File

@@ -1,211 +0,0 @@
import postgres from 'postgres'
import crossFetch from 'cross-fetch'
import { faker } from '@faker-js/faker'
import {
AuthResponse,
createClient,
SupabaseClient,
SupabaseClientOptions,
User,
UserAttributes,
UserResponse,
} from '@supabase/supabase-js'
import { JasmineAllureReporter, step } from '../../.jest/jest-custom-reporter'
export abstract class Hooks {
static sql = postgres({
host: process.env.SUPABASE_DB_HOST,
port: parseInt(process.env.SUPABASE_DB_PORT),
database: 'postgres',
username: 'postgres',
password: process.env.SUPABASE_DB_PASS,
})
@step('terminate sql connection')
static async after(): Promise<any> {
try {
Hooks.sql.end({ timeout: 100 })
return Promise.resolve(null)
} catch (err) {
return Promise.reject(err)
}
}
@step('Create Supabase client')
createSupaClient(
url: string,
key: string,
options: SupabaseClientOptions<'public'> = {}
): SupabaseClient {
options.auth = options.auth || {}
options.auth.persistSession = false
return createClient(url, key, options)
}
@step('Create a valid user')
async createUser(data: object = {}): Promise<{
email: string
password: string
username: string
id: 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(),
id: '',
}
const {
error: signUpError,
data: { user },
} = await this.signUp(supabase, fakeUser, {
data: data,
})
expect(signUpError).toBeNull()
expect(user).not.toBeNull()
fakeUser.id = user.id
return fakeUser
}
// todo: rework this
@step((token: string) => `verify with token ${token}`)
async verify(token: string, email: string): Promise<Response> {
return crossFetch(`${process.env.SUPABASE_GOTRUE}/verify`, {
method: 'POST',
body: JSON.stringify({
type: 'signup',
token: token,
email: email,
}),
})
}
@step((user: User) => `get confirmation token for user ${user.id}`)
async getConfirmationToken(user: User): Promise<[{ confirmation_token: any }]> {
return Hooks.sql`
select confirmation_token
from auth.users
where id = ${user.id}
`
}
@step('I sign up with a valid email and password')
async signUp(
supabase: SupabaseClient,
{
email = faker.internet.exampleEmail(),
password = faker.internet.password(),
}: {
email?: string
password?: string
} = {},
options: {
redirectTo?: string
data?: object
captchaToken?: string
} = {}
): Promise<AuthResponse> {
return supabase.auth.signUp({
email: email,
password: password,
options: options,
})
}
@step('Check if I am being able to log out')
async signOut(supabase: SupabaseClient): Promise<{ error: any }> {
return supabase.auth.signOut()
}
@step('Get user data, if there is a logged in user')
getUser(supabase: SupabaseClient) {
return supabase.auth.getUser()
}
@step((user: User) => `Get user by ID (${user.id}) from Supabase auth schema`)
async selectUser(user: User): Promise<[{ email: string }]> {
return Hooks.sql`
select
email
from auth.users
where
id = ${user.id}
`
}
@step('I sign up with a valid email and password')
async signUpByPhone(
supabase: SupabaseClient,
{
phone = faker.phone.phoneNumber(),
password = faker.internet.password(),
}: {
phone?: string
password?: string
} = {},
options: {
redirectTo?: string
data?: object
} = {}
): Promise<AuthResponse> {
return supabase.auth.signUp({
phone: phone,
password: password,
options: options,
})
}
@step('User inserts profile')
async insertProfile(
supabase: SupabaseClient,
user: {
id: string
},
fakeUser: {
username: string
}
): Promise<{ data: any; error: any }> {
return await supabase
.from('profiles')
.insert({
id: user.id,
username: fakeUser.username,
})
.select()
}
@step('I can get my profile via postgREST')
async getUserProfile(supabase: SupabaseClient): Promise<{ data: any; error: any }> {
return supabase.from('profiles').select().maybeSingle()
}
@step('Update user info')
async updateUser(supabase: SupabaseClient, attr: UserAttributes): Promise<UserResponse> {
return supabase.auth.updateUser(attr)
}
@step('Create signed in supabase client')
async createSignedInSupaClient() {
// 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 {
data: { user },
error: signInError,
} = await supabase.auth.signInWithPassword({
email: fakeUser.email,
password: fakeUser.password,
})
expect(signInError).toBeNull()
fakeUser.id = user.id
return { supabase, user: fakeUser }
}
}

View File

@@ -1,86 +0,0 @@
import { suite, test, timeout } from '@testdeck/jest'
import { faker } from '@faker-js/faker'
import { Severity } from 'allure-js-commons'
import { FEATURE } from '../templates/enums'
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
@suite('postgrest')
class PostgREST extends Hooks {
@feature(FEATURE.POSTGREST)
@severity(Severity.BLOCKER)
@description('User can insert profile for himself according to RLS to profiles table')
@test
async 'insert row'() {
const { supabase, user } = await this.createSignedInSupaClient()
// check if user can insert profile for himself
const {
data: [profileInserted],
error: errorInsert,
} = await this.insertProfile(supabase, user, user)
expect(errorInsert).toBeNull()
expect(profileInserted.username).toMatch(user.username)
}
@feature(FEATURE.POSTGREST)
@severity(Severity.BLOCKER)
@description('User can select his profile from profiles table')
@test
async 'select row'() {
const { supabase, user } = await this.createSignedInSupaClient()
// insert profile for user
await this.insertProfile(supabase, user, user)
const { data: profileGot, error } = await this.getUserProfile(supabase)
expect(error).toBeNull()
expect(profileGot.username).toMatch(user.username)
}
@feature(FEATURE.POSTGREST)
@severity(Severity.BLOCKER)
@description('User can update his profile in profiles table')
@test
async 'update row'() {
const { supabase, user } = await this.createSignedInSupaClient()
// insert profile for user
await this.insertProfile(supabase, user, user)
const newName = faker.internet.userName()
// update profile for user
const { data: updUser, error } = await supabase
.from('profiles')
.update({
id: user.id,
username: newName,
})
.select()
expect(error).toBeNull()
expect(updUser.length).toEqual(1)
expect(updUser[0].username).toMatch(newName)
const { data: profileGot } = await this.getUserProfile(supabase)
expect(profileGot.username).toMatch(newName)
}
@feature(FEATURE.POSTGREST)
@severity(Severity.BLOCKER)
@description('User can delete his profile in profiles table')
@test
async 'delete row'() {
const { supabase, user } = await this.createSignedInSupaClient()
// insert profile for user
await this.insertProfile(supabase, user, user)
// delete profile for user
const { error } = await supabase.from('profiles').delete().eq('id', user.id).select()
expect(error).toBeNull()
const { data: profileGot } = await this.getUserProfile(supabase)
expect(profileGot).toBeNull()
}
}

View File

@@ -1,316 +0,0 @@
import { params, suite, test, timeout } from '@testdeck/jest'
import { faker } from '@faker-js/faker'
import { Severity } from 'allure-js-commons'
import { RealtimeChannel } from '@supabase/supabase-js'
import { FEATURE } from '../templates/enums'
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
@suite('realtime')
@timeout(30000)
class Realtime extends Hooks {
@feature(FEATURE.REALTIME)
@severity(Severity.BLOCKER)
@description('When you call "on" table then connected realtime client should be returned')
@test
async '[skip-stage] connect to realtime'() {
const { supabase } = await this.createSignedInSupaClient()
const channel = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) =>
console.log(payload)
)
channel.subscribe()
expect(channel).toBeDefined()
const err = await this.waitForChannelJoined(channel)
await new Promise((resolve) => setTimeout(resolve, 1000))
expect(err).toBeNull()
const ok = await supabase.removeChannel(channel)
expect(ok).toBe('ok')
}
@feature(FEATURE.REALTIME)
@severity(Severity.BLOCKER)
@description('When you subscrive to realtime, you have to receive updates')
@timeout(60000)
@test
async '[skip-stage] receive event when connected to realtime'() {
let res: any
let t: NodeJS.Timeout
const { supabase, user } = await this.createSignedInSupaClient()
let payloadReceived = (payload: any) => {
if (payload?.eventType !== 'INSERT') {
return
}
clearTimeout(t)
expect(payload.schema).toBe('public')
expect(payload.table).toBe('profiles')
expect(payload.new.id).toBe(user.id)
expect(payload.new.username).toBe(user.username)
expect(payload.old).toEqual({})
expect(payload.error).toBeUndefined()
res(null)
}
const channel = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, payloadReceived)
channel.subscribe()
expect(channel).toBeDefined()
await this.waitForChannelJoined(channel)
// we should wait some time seconds to connect to database changes
await new Promise((resolve) => setTimeout(resolve, 10000))
const eventPromise = new Promise((resolve) => {
res = resolve
new Promise(() => {
t = setTimeout(() => resolve(new Error('timeout')), 30000)
})
})
await this.insertProfile(supabase, user, user)
expect(await eventPromise).toBeNull()
const ok = await supabase.removeChannel(channel)
expect(ok).toBe('ok')
}
@feature(FEATURE.REALTIME)
@severity(Severity.NORMAL)
@description('When you call "on" table but not subscribe then no events have to be returned')
@test
async 'you should get no events until subscribe'() {
const { supabase, user } = await this.createSignedInSupaClient()
const channel = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => {
console.log(payload)
expect('event received').toBe('should not receive event')
})
expect(channel).toBeDefined()
await this.insertProfile(supabase, user, user)
// wait for 1 second to see if we receive any events
await new Promise((resolve) => setTimeout(resolve, 1000))
expect(channel._isClosed).toBeTruthy()
const ok = await supabase.removeChannel(channel)
expect(ok).toBe('ok')
}
@feature(FEATURE.REALTIME)
@severity(Severity.BLOCKER)
@description(
'When you create 2 subs (1 subscribed and 1 not yet) then both should be returned on get subs'
)
@test
async 'get supabase client subscriptions'() {
const { supabase } = await this.createSignedInSupaClient()
const channel1 = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => {
console.log(payload)
expect('event received').toBe('should not receive event')
})
const channel2 = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => {
console.log(payload)
expect('event received').toBe('should not receive event')
})
const channels = supabase.getChannels()
expect(channels).toEqual(expect.arrayContaining([channel1, channel2]))
supabase.removeAllChannels()
expect(supabase.getChannels().length).toEqual(0)
}
@feature(FEATURE.REALTIME)
@severity(Severity.NORMAL)
@description('When you subscribe on table then corresponding events have to be returned')
@params.skip({ event: 'INSERT', returnedTypes: ['insert'] })
@params.skip({ event: 'UPDATE', returnedTypes: ['update'] })
@params.skip({ event: 'DELETE', returnedTypes: ['delete'] })
@params.skip({ event: '*', returnedTypes: ['insert', 'update', 'delete'] })
async 'subscribe on table'() {
// todo
}
@feature(FEATURE.REALTIME)
@severity(Severity.NORMAL)
@description(
'When you create multiple subscriptions then corresponding events have to be returned'
)
@params.skip({
events: [
{ event: 'INSERT', table: 'rt_table1' },
{ event: '*', table: 'rt_table1' },
],
returnedIds: [[], []],
})
@params.skip({
events: [
{ event: 'UPDATE', table: 'rt_table1' },
{ event: 'DELETE', table: 'rt_table1' },
],
returnedIds: [[], []],
})
@params.skip({
events: [
{ event: 'INSERT', table: 'rt_table1' },
{ event: 'INSERT', table: 'rt_table2' },
],
returnedIds: [[], []],
})
async 'multiple subscriptions'() {
// todo
}
@feature(FEATURE.REALTIME)
@severity(Severity.NORMAL)
@description('When you subscribe on table with RLS then corresponding events have to be returned')
@params.skip({ event: 'INSERT', returnedTypes: ['insert'] })
@params.skip({ event: 'UPDATE', returnedTypes: ['update'] })
@params.skip({ event: 'DELETE', returnedTypes: ['delete'] })
@params.skip({ event: '*', returnedTypes: ['insert', 'update', 'delete'] })
async 'subscribe on table with RLS'() {
// todo
}
@feature(FEATURE.REALTIME)
@severity(Severity.CRITICAL)
@description('When you subscribe on table with RLS then only allowed events have to be returned')
@params.skip({ event: 'INSERT', returnedIds: [] })
@params.skip({ event: 'UPDATE', returnedIds: [] })
@params.skip({ event: 'DELETE', returnedIds: [] })
@params.skip({ event: '*', returnedIds: [] })
async 'subscribe on table with RLS policies'() {
// todo
}
@feature(FEATURE.REALTIME)
@severity(Severity.CRITICAL)
@description(
'When you subscribe on table with 2 clients then they have to receive different events'
)
@test.skip
async 'subscribe from 2 clients'() {
// todo
}
@feature(FEATURE.REALTIME)
@severity(Severity.NORMAL)
@description(
'When you subscribe on table with RLS policies then service key subscription has to bypass RLS'
)
@params.skip({ event: 'INSERT', returnedTypes: ['insert'] })
@params.skip({ event: 'UPDATE', returnedTypes: ['update'] })
@params.skip({ event: 'DELETE', returnedTypes: ['delete'] })
@params.skip({ event: '*', returnedTypes: ['insert', 'update', 'delete'] })
async 'subscribe on RLS table with service key'() {
// todo
}
@feature(FEATURE.REALTIME)
@severity(Severity.CRITICAL)
@description('When you unsubscribe from table then no events have to be returned')
@test
async '[skip-stage] unsubscribe from table'() {
const { supabase, user } = await this.createSignedInSupaClient()
const channel = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => {
console.log(payload)
expect('event received').toBe('should not receive event')
})
channel.subscribe()
// wait for subscription to postgres
await new Promise((resolve) => setTimeout(resolve, 8000))
const ok = await channel.unsubscribe()
expect(ok).toEqual('ok')
await this.insertProfile(supabase, user, user)
// wait for 1 second to see if we receive any events
await new Promise((resolve) => setTimeout(resolve, 1000))
expect(channel._isClosed).toBeTruthy()
await supabase.removeChannel(channel)
}
@feature(FEATURE.REALTIME)
@severity(Severity.CRITICAL)
@description('When you remove one subscription then only events from another have to be returned')
@test
async '[skip-stage] remove one subscription from client'() {
const { supabase, user } = await this.createSignedInSupaClient()
const channel = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => {
console.log(payload)
expect('event received').toBe('should not receive event')
})
channel.subscribe()
// wait for subscription to postgres
await new Promise((resolve) => setTimeout(resolve, 8000))
const ok = await supabase.removeChannel(channel)
expect(ok).toEqual('ok')
await this.insertProfile(supabase, user, user)
// wait for 1 second to see if we receive any events
await new Promise((resolve) => setTimeout(resolve, 1000))
expect(channel._isClosed).toBeTruthy()
}
@feature(FEATURE.REALTIME)
@severity(Severity.CRITICAL)
@description('When you remove all subscription then no events have to be returned')
@test
async '[skip-stage] remove all subscriptions from client'() {
const { supabase, user } = await this.createSignedInSupaClient()
const channel = supabase
.channel('profiles')
.on('postgres_changes', { event: '*', schema: 'public' }, (payload: any) => {
console.log(payload)
expect('event received').toBe('should not receive event')
})
channel.subscribe()
// wait for subscription to postgres
await new Promise((resolve) => setTimeout(resolve, 8000))
const ok = await supabase.removeAllChannels()
expect(ok).toEqual(expect.arrayContaining(['ok']))
await this.insertProfile(supabase, user, user)
// wait for 1 second to see if we receive any events
await new Promise((resolve) => setTimeout(resolve, 1000))
expect(channel._isClosed).toBeTruthy()
}
@step('Wait until channel is joined')
async waitForChannelJoined(channel: RealtimeChannel): Promise<Error> {
for (let i = 0; i < 30; i++) {
if (channel._isJoined()) {
return null
}
if (channel._isLeaving()) {
return new Error('Channel is leaving')
}
if (channel._isClosed()) {
return new Error('Channel is closed')
}
await new Promise((resolve) => setTimeout(resolve, 100))
}
return new Error("Channel didn't join in 3 seconds")
}
}

View File

@@ -1,134 +0,0 @@
import { suite, test, timeout } from '@testdeck/jest'
import { Severity } from 'allure-js-commons'
import { SupabaseClient } from '@supabase/supabase-js'
import { FEATURE } from '../templates/enums'
import { description, feature, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
import { PendingQuery, Row } from 'postgres'
@suite('rpc')
@timeout(30000)
class Procedures extends Hooks {
@feature(FEATURE.RPC)
@severity(Severity.BLOCKER)
@description('When you call rpc then you are able to receive its result')
@test
async 'call rpc and get result'() {
await this.createFunction(Procedures.sql`
CREATE OR REPLACE FUNCTION public.test_procedure() RETURNS int language plpgsql as $$
declare
profile_count integer;
begin
select count(*)
into profile_count
from profiles;
return profile_count;
end;
$$;`)
const { supabase, user } = await this.createSignedInSupaClient()
await this.insertProfile(supabase, user, user)
const result = await this.callRpc(supabase, 'test_procedure')
expect(result.error).toBeNull()
expect(result.data).toBeDefined()
expect(result.data).toBeGreaterThanOrEqual(1)
await this.dropFunction(Procedures.sql`test_procedure()`)
}
@feature(FEATURE.RPC)
@severity(Severity.BLOCKER)
@description('When you call rpc, params should be passed properly')
@test
async 'call rpc method that has params'() {
await this.createFunction(Procedures.sql`
CREATE OR REPLACE FUNCTION public.test_procedure(filter text)
RETURNS int language plpgsql as $$
declare
profile_count integer;
begin
select count(*)
into profile_count
from profiles
where username LIKE '%' || filter || '%';
return profile_count;
end;
$$;`)
const { supabase, user } = await this.createSignedInSupaClient()
await this.insertProfile(supabase, user, user)
const result = await this.callRpc(supabase, 'test_procedure', { filter: user.username })
expect(result.error).toBeNull()
expect(result.data).toBeDefined()
expect(result.data).toBeGreaterThanOrEqual(1)
await this.dropFunction(Procedures.sql`test_procedure(filter text)`)
}
@feature(FEATURE.RPC)
@severity(Severity.NORMAL)
@description('When you call rpc with head param, no data should be returned')
@test
async 'call rpc with head option'() {
await this.createFunction(Procedures.sql`
CREATE OR REPLACE FUNCTION public.test_procedure() RETURNS int language plpgsql as $$
declare
profile_count integer;
begin
select count(*)
into profile_count
from profiles;
return profile_count;
end;
$$;`)
const { supabase, user } = await this.createSignedInSupaClient()
await this.insertProfile(supabase, user, user)
const result = await this.callRpc(supabase, 'test_procedure', {}, { head: true })
expect(result.error).toBeNull()
expect(result.data).toBeNull()
await this.dropFunction(Procedures.sql`test_procedure()`)
}
@step('create function')
private async createFunction(body: PendingQuery<Row[]>) {
await Procedures.sql`${body}`
await Procedures.sql`NOTIFY pgrst, 'reload schema';`
}
@step('drop function')
private async dropFunction(signature: PendingQuery<Row[]>) {
await Procedures.sql`DROP FUNCTION public.${signature};`
}
@step('call supabase rpc')
private async callRpc(
supabase: SupabaseClient,
name: string,
args?: any,
options?: {
head?: boolean
count?: 'exact' | 'planned' | 'estimated'
}
) {
let result = await supabase.rpc(name, args, options)
for (let i = 1; i <= 5; i++) {
if (result.error) {
await new Promise((resolve) => setTimeout(resolve, 0.5 * 1000 * i))
result = await supabase.rpc(name, args, options)
} else {
break
}
}
return result
}
}

View File

@@ -1,424 +0,0 @@
import { params, suite, test } from '@testdeck/jest'
import { faker } from '@faker-js/faker'
import { Severity } from 'allure-js-commons'
import { createClient, Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js'
import { Bucket } from '@supabase/storage-js'
import { FEATURE } from '../templates/enums'
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
import { Hooks } from './hooks'
import fetch from 'cross-fetch'
@suite('storage')
class Storage extends Hooks {
static buckets: Pick<Bucket, 'name'>[] = [] as any
static async after() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const requests = []
for (const bucket of this.buckets) {
requests.push(
new Promise((resolve) =>
(async () => {
await supabase.storage.emptyBucket(bucket.name)
await supabase.storage.deleteBucket(bucket.name)
resolve(null)
})()
)
)
}
await Promise.all(requests)
await Hooks.after()
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('When you create public bucket then it has to be available')
@params({ public: true })
@params({ public: false })
async 'create bucket'(params: { public: boolean }) {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucketName = faker.unique(faker.random.word)
const { data: bucket, error } = await supabase.storage.createBucket(bucketName, {
public: params.public,
})
expect(error).toBeNull()
expect(bucket).toBeDefined()
expect(bucket.name).toBe(bucketName)
Storage.buckets.push(bucket)
const { data: gotBucket, error: error2 } = await supabase.storage.getBucket(bucketName)
expect(error2).toBeNull()
expect(gotBucket).toBeDefined()
expect(gotBucket.name).toBe(bucketName)
expect(gotBucket.public).toBe(params.public)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('There has to be default RLS policy to not allow users to create bucket')
@params({ public: true })
@params({ public: false })
async 'user cannot create bucket because of RLS'(params: { public: boolean }) {
const { supabase } = await this.createSignedInSupaClient()
const bucketName = faker.unique(faker.random.word)
const { data: bucket, error } = await supabase.storage.createBucket(bucketName, {
public: params.public,
})
expect(error).not.toBeNull()
expect(error.message).toContain('row-level security')
expect(error.message).toContain('"buckets"')
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('list buckets should return all buckets')
@test
async 'list buckets as admin'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket1 = await this.createBucket()
const bucket2 = await this.createBucket()
const { data: buckets, error } = await supabase.storage.listBuckets()
expect(buckets.length).toBeGreaterThanOrEqual(2)
expect(buckets.map((b) => b.name)).toEqual(expect.arrayContaining([bucket1.name, bucket2.name]))
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('get bucket should return bucket info')
@test
async 'get bucket'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const { data: gotBucket, error } = await supabase.storage.getBucket(bucket.id)
expect(error).toBeNull()
expect(gotBucket).toEqual(bucket)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('update bucket should change bucket both public->private and back')
@params({ public: true })
@params({ public: false })
async 'update bucket'(params: { public: boolean }) {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket(params.public)
const {
data: { message },
error,
} = await supabase.storage.updateBucket(bucket.id, {
public: !params.public,
})
expect(error).toBeNull()
expect(message).toBe('Successfully updated')
bucket.public = !params.public
const { data: gotBucket } = await supabase.storage.getBucket(bucket.id)
expect(gotBucket).toEqual(bucket)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('get bucket should return bucket info')
@test
async 'delete bucket'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const {
data: { message },
error,
} = await supabase.storage.deleteBucket(bucket.id)
expect(error).toBeNull()
expect(message).toBe('Successfully deleted')
const { data: buckets } = await supabase.storage.listBuckets()
expect(
buckets.map((b) => {
return { name: b.name, id: b.id }
})
).not.toContain({ name: bucket.name, id: bucket.id })
Storage.buckets = Storage.buckets.filter((b) => b.name != bucket.name)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('upload to bucket')
@test
async 'upload to bucket'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const file = {
path: faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
const {
data: { path },
error,
} = await supabase.storage.from(bucket.name).upload(file.path, file.data)
expect(error).toBeNull()
expect(path).toEqual(file.path)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('list files in bucket')
@test
async 'list files'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const files: { path: string; data: string }[] = []
const requests: Promise<void>[] = []
for (let i = 0; i < 3; i++) {
requests.push(
new Promise((resolve) => {
const dir = faker.random.word()
const file = {
path: dir + '/' + faker.random.word() + '.txt',
data: faker.lorem.sentence(),
}
files.push(file)
const p1 = supabase.storage.from(bucket.name).upload(file.path, file.data)
const file2 = {
path: dir + '/' + faker.random.word() + '.txt',
data: faker.lorem.sentence(),
}
files.push(file2)
const p2 = supabase.storage.from(bucket.name).upload(file2.path, file2.data)
Promise.all([p1, p2]).finally(() => resolve())
})
)
}
await Promise.all(requests)
const { data: dirs, error: error1 } = await supabase.storage.from(bucket.name).list()
expect(error1).toBeNull()
expect(dirs.map((d) => d.name)).toEqual(
expect.arrayContaining([...new Set(files.map((f) => f.path.split('/')[0]))])
)
const dir = files[0].path.split('/')[0]
const { data: filesInDir, error: error2 } = await supabase.storage.from(bucket.name).list(dir)
expect(error2).toBeNull()
expect(filesInDir.map((d) => d.name)).toEqual(
expect.arrayContaining(
files.filter((f) => f.path.split('/')[0] === dir).map((f) => f.path.split('/')[1])
)
)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('download file')
@test
async 'download file from bucket'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const file = {
path: faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const { data, error } = await supabase.storage.from(bucket.name).download(file.path)
expect(error).toBeNull()
expect(await data.text()).toEqual(file.data)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('move file')
@test
async '[skip-local] move file in bucket'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const file = {
path: faker.random.word() + '/' + faker.random.word() + '.txt',
newPath: faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const { error } = await supabase.storage.from(bucket.name).move(file.path, file.newPath)
expect(error).toBeNull()
const { data: filesRoot } = await supabase.storage.from(bucket.name).list()
expect(filesRoot.map((f) => f.name)).not.toContain(file.path.split('/')[0])
const { data: files } = await supabase.storage
.from(bucket.name)
.list(file.newPath.split('/')[0])
expect(files.map((f) => f.name)).toEqual(expect.arrayContaining([file.newPath.split('/')[1]]))
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('copy file')
@test
async '[skip-local] copy file in bucket'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const file = {
path: faker.random.word() + '/' + faker.random.word() + '.txt',
newPath: faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const { error } = await supabase.storage.from(bucket.name).copy(file.path, file.newPath)
expect(error).toBeNull()
const { data: filesRoot } = await supabase.storage.from(bucket.name).list()
expect(filesRoot.map((f) => f.name)).toEqual(
expect.arrayContaining([file.path.split('/')[0], file.newPath.split('/')[0]])
)
const { data: filesNew } = await supabase.storage
.from(bucket.name)
.list(file.newPath.split('/')[0])
expect(filesNew.map((f) => f.name)).toEqual(
expect.arrayContaining([file.newPath.split('/')[1]])
)
const { data: files } = await supabase.storage.from(bucket.name).list(file.path.split('/')[0])
expect(files.map((f) => f.name)).toEqual(expect.arrayContaining([file.path.split('/')[1]]))
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('get public link to file in the public bucket')
@test
async 'get public link to file'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const file = {
path: faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const {
data: { publicUrl },
} = supabase.storage.from(bucket.name).getPublicUrl(file.path)
expect(publicUrl).toBeDefined()
expect(publicUrl.length).toBeGreaterThan(1)
expect(publicUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`))
const resp = await fetch(publicUrl)
expect(resp.status).toEqual(200)
expect(await resp.text()).toEqual(file.data)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('get public link to file in the private bucket')
@test
async 'get public link to private file'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket(false)
const file = {
path: faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const {
data: { publicUrl },
} = supabase.storage.from(bucket.name).getPublicUrl(file.path)
expect(publicUrl).toBeDefined()
expect(publicUrl.length).toBeGreaterThan(1)
expect(publicUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`))
const resp = await fetch(publicUrl)
expect(resp.status).toEqual(400) // todo 400 should be 404 too I guess
const error = await resp.json()
expect(error.statusCode).toBe('404') // todo should be a number?
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('get signed link to file in the bucket')
@params({ public: true })
@params({ public: false })
async 'get signed link to file'(params: { public: boolean }) {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket(params.public)
const file = {
path: faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const {
data: { signedUrl },
error,
} = await supabase.storage.from(bucket.name).createSignedUrl(file.path, 10000)
expect(error).toBeNull()
expect(signedUrl).toBeDefined()
expect(signedUrl.length).toBeGreaterThan(1)
expect(signedUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`))
const resp = await fetch(signedUrl)
expect(resp.status).toEqual(200)
expect(await resp.text()).toEqual(file.data)
}
@feature(FEATURE.STORAGE)
@severity(Severity.BLOCKER)
@description('update file check if it will change')
@test
async 'update file'() {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucket = await this.createBucket()
const file = {
path: faker.random.word() + '/' + faker.random.word() + '/' + faker.random.word() + '.txt',
data: faker.lorem.paragraph(),
}
await supabase.storage.from(bucket.name).upload(file.path, file.data)
const {
data: { publicUrl },
} = supabase.storage.from(bucket.name).getPublicUrl(file.path)
expect(publicUrl).toBeDefined()
expect(publicUrl.length).toBeGreaterThan(1)
expect(publicUrl).toMatch(new RegExp(`^http[s]?://.*storage.*/${bucket.name}/.*`))
const newFile = {
path: file.path,
data: faker.lorem.paragraph(10),
}
await supabase.storage.from(bucket.name).update(newFile.path, newFile.data)
const resp = await fetch(publicUrl)
expect(resp.status).toEqual(200)
expect(await resp.text()).toEqual(newFile.data)
}
@step('create bucket')
async createBucket(pub = true) {
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
const bucketName = faker.unique(faker.random.word)
const { data: bucket, error } = await supabase.storage.createBucket(bucketName, {
public: pub,
})
expect(error).toBeNull()
expect(bucket).toBeDefined()
expect(bucket.name).toBe(bucketName)
Storage.buckets.push(bucket)
const { data: buckets } = await supabase.storage.listBuckets()
return buckets.find((b) => b.name === bucketName)
}
}

View File

@@ -1,44 +0,0 @@
import allure
@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()
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

View File

@@ -1,50 +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! */
})
})
})

View File

@@ -1,11 +0,0 @@
export enum FEATURE {
AUTHENTICATION = 'authentication',
AUTH_ADMIN = 'auth_admin',
REALTIME = 'realtime',
STORAGE = 'storage',
FUNCTIONS = 'functions',
RPC = 'rpc',
POSTGREST = 'postgrest',
FILTERS = 'postgrest_filters',
MODIFIERS = 'postgrest_modifiers',
}

View File

@@ -1,24 +0,0 @@
import type { Config } from '@jest/types'
const config: Config.InitialOptions = {
preset: 'ts-jest',
transform: {
'^.+\\.ts?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'cjs', 'json', 'node'],
globalSetup: '<rootDir>/.jest/jest-env.ts',
globalTeardown: '<rootDir>/.jest/teardown.ts',
// setupFiles: ['<rootDir>/.jest/jest-env.js'],
setupFilesAfterEnv: ['<rootDir>/.jest/jest-custom-reporter.ts'],
testRunner: 'jest-jasmine2',
testTimeout: 15000,
coverageThreshold: {
global: {
branches: 0,
functions: 0,
lines: 0,
statements: 0,
},
},
}
export default config

View File

@@ -1,39 +0,0 @@
{
"name": "supabase-tests",
"version": "1.0.0",
"description": "These tests can be run with Docker.",
"scripts": {
"preinstall": "npx only-allow pnpm",
"docker:up": "cd ../docker && ENABLE_EMAIL_AUTOCONFIRM=true docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml up -d",
"docker:down": "cd ../docker && docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml down",
"test": "jest",
"test:local": "jest --testNamePattern '^((?!\\[skip-local\\]).)*$' --testPathPattern '^((?!realtime).)*$' --detectOpenHandles",
"test:stage": "jest --testNamePattern '^((?!\\[skip-stage\\]).)*$' --detectOpenHandles",
"test:prod": "jest --detectOpenHandles",
"allure:generate": "rm -rf allure-report && node_modules/allure-commandline/bin/allure generate",
"allure:serve": "node_modules/allure-commandline/bin/allure serve",
"test:report": "pnpm run allure:generate && pnpm run allure:serve",
"clean": "rimraf node_modules",
"format": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,md}\""
},
"author": "Supabase, Inc.",
"license": "MIT",
"devDependencies": {
"@faker-js/faker": "^6.1.2",
"@supabase/supabase-js": "^2.44.3",
"@testdeck/jest": "^0.3.3",
"@types/jest": "^29.5.4",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"allure-commandline": "^2.17.2",
"allure-js-commons": "^2.0.0-beta.14",
"cross-fetch": "^3.1.5",
"jest": "^29.7.0",
"jest-allure2-adapter": "^0.3.12",
"jest-environment-jsdom": "^29.7.0",
"postgres": "^3.0.5",
"ts-jest": "^29.1.1",
"typescript": "~5.5.0"
}
}

View File

@@ -1,3 +0,0 @@
# Supabase
.branches
.temp

View File

@@ -1,71 +0,0 @@
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
# directory name when running `supabase init`.
project_id = "tests"
[api]
# Port to use for the API URL.
port = 54321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
# endpoints. public and storage are always included.
schemas = []
# Extra schemas to add to the search_path of every request.
extra_search_path = ["extensions"]
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
# for accidental or malicious requests.
max_rows = 1000
[db]
# Port to use for the local database URL.
port = 54322
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
# server_version;` on the remote database to check.
major_version = 14
[studio]
# Port to use for Supabase Studio.
port = 54323
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
# are monitored, and you can view the emails that would have been sent from the web interface.
[inbucket]
# Port to use for the email testing server web interface.
port = 54324
smtp_port = 54325
pop3_port = 54326
[auth]
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://localhost:3000"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://localhost:3000"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
# week).
jwt_expiry = 3600
# Allow/disallow new user signups to your project.
enable_signup = true
[auth.email]
# Allow/disallow new user signups via email to your project.
enable_signup = true
# If enabled, a user will be required to confirm any email change on both the old, and new email
# addresses. If disabled, only the new email is required to confirm.
double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = false
[auth.phone]
# Allow/disallow new user signups via phone to your project.
enable_signup = true
double_confirm_changes = true
enable_confirmations = false
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`.
[auth.external.apple]
enabled = false
client_id = ""
secret = ""
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
# or any other third-party OIDC providers.
url = ""

View File

@@ -1,50 +0,0 @@
create table if not exists profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
primary key (id),
unique(username),
constraint username_length check (char_length(username) >= 3)
);
alter table profiles enable row level security;
create policy "Public profiles are viewable by the owner."
on profiles for select
using ( auth.uid() = id );
create policy "Users can insert their own profile."
on profiles for insert
with check ( auth.uid() = id );
create policy "Users can update own profile."
on profiles for update
using ( auth.uid() = id );
create policy "Users can delete own profile."
on profiles for delete
using ( auth.uid() = id );
-- Set up Realtime
drop publication if exists supabase_realtime;
create publication supabase_realtime;
alter publication supabase_realtime add table profiles;
-- Set up Storage
insert into storage.buckets (id, name)
values ('avatars', 'avatars') on conflict do nothing;
create policy "Avatar images are publicly accessible."
on storage.objects for select
using ( bucket_id = 'avatars' );
create policy "Anyone can upload an avatar."
on storage.objects for insert
with check ( bucket_id = 'avatars' );
create policy "Anyone can update an avatar."
on storage.objects for update
with check ( bucket_id = 'avatars' );

View File

@@ -1,29 +0,0 @@
{
"compilerOptions": {
"target": "ES2021",
"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
}