mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into hendrik-e2e
This commit is contained in:
commit
a50a837be4
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -64,7 +64,7 @@ jobs:
|
||||
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
|
||||
#- name: Repository Dispatch
|
||||
# uses: peter-evans/repository-dispatch@e58f0e551cf92535579bb196c65d215dc5bbdbc2 # v3.0.0
|
||||
# uses: peter-evans/repository-dispatch@6846232b0e1bfd17c14dce7ac13fd3fcefe22c0c # v3.0.0
|
||||
# with:
|
||||
# token: ${{ github.token }}
|
||||
# event-type: trigger-ocelot-build-success
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.ocelot.social
|
||||
uses: peter-evans/repository-dispatch@e58f0e551cf92535579bb196c65d215dc5bbdbc2 # v3.0.0
|
||||
uses: peter-evans/repository-dispatch@6846232b0e1bfd17c14dce7ac13fd3fcefe22c0c # v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.yunite.me
|
||||
uses: peter-evans/repository-dispatch@e58f0e551cf92535579bb196c65d215dc5bbdbc2 # v3.0.0
|
||||
uses: peter-evans/repository-dispatch@6846232b0e1bfd17c14dce7ac13fd3fcefe22c0c # v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
|
||||
11
.github/workflows/test-backend.yml
vendored
11
.github/workflows/test-backend.yml
vendored
@ -32,8 +32,8 @@ jobs:
|
||||
|
||||
- name: Neo4J | Build 'community' image
|
||||
run: |
|
||||
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
|
||||
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/neo4j.tar
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml build neo4j
|
||||
docker save "ghcr.io/ocelot-social-community/ocelot-social/neo4j:community" > /tmp/neo4j.tar
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache-neo4j
|
||||
@ -53,8 +53,8 @@ jobs:
|
||||
|
||||
- name: backend | Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
|
||||
docker save "ocelotsocialnetwork/backend:test" > /tmp/backend.tar
|
||||
docker compose -f docker-compose.yml -f docker-compose.test.yml build backend
|
||||
docker save "ghcr.io/ocelot-social-community/ocelot-social/backend:test" > /tmp/backend.tar
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache-backend
|
||||
@ -112,8 +112,7 @@ jobs:
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: backend | docker compose
|
||||
# doesn't work without the --build flag - this either means we should not load the cached images or cache the correct image
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach backend --build
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach backend
|
||||
|
||||
- name: backend | Initialize Database
|
||||
run: docker compose exec -T backend yarn db:migrate init
|
||||
|
||||
44
CHANGELOG.md
44
CHANGELOG.md
@ -4,8 +4,52 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [3.11.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.10.1...3.11.0)
|
||||
|
||||
- remove expect package [`#8738`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8738)
|
||||
- build(deps-dev): bump eslint-plugin-import in /backend [`#8705`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8705)
|
||||
- build(deps): bump the metascraper group in /backend with 12 updates [`#8717`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8717)
|
||||
- build(deps-dev): bump dotenv from 16.5.0 to 17.0.0 [`#8720`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8720)
|
||||
- build(deps-dev): bump eslint-plugin-jest in /backend [`#8706`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8706)
|
||||
- build(deps): bump @aws-sdk/lib-storage in /backend [`#8723`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8723)
|
||||
- build(deps): bump @aws-sdk/client-s3 from 3.832.0 to 3.839.0 in /backend [`#8722`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8722)
|
||||
- refactor(backend): put config into context [`#8603`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8603)
|
||||
- build(deps): bump dotenv from 16.5.0 to 17.0.0 in /backend [`#8726`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8726)
|
||||
- build(deps-dev): bump @types/node from 24.0.3 to 24.0.6 in /backend [`#8721`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8721)
|
||||
- fix(webapp): added option for slug [`#8659`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8659)
|
||||
- build(deps-dev): bump prettier from 3.5.3 to 3.6.2 in /backend [`#8729`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8729)
|
||||
- build(deps-dev): bump eslint-import-resolver-typescript in /backend [`#8725`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8725)
|
||||
- build(deps-dev): bump @types/lodash from 4.17.18 to 4.17.19 in /backend [`#8727`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8727)
|
||||
- build(deps-dev): bump eslint-plugin-prettier in /backend [`#8728`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8728)
|
||||
- build(deps): bump node from 24.2.0-alpine to 24.3.0-alpine in /backend [`#8730`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8730)
|
||||
- build(deps): bump peter-evans/repository-dispatch [`#8731`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8731)
|
||||
- fix(backend): mask jwt token in log [`#8737`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8737)
|
||||
- refactor(backend): fix tests for #8714 [`#8716`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8716)
|
||||
- fix(backend): refactor S3 usage and always apply protocol fix [`#8714`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8714)
|
||||
- refactor(docker): neo4j image naming inconsistency in docker compose files [`#8736`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8736)
|
||||
- feat(backend): all db node properties [`#8635`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8635)
|
||||
- fix(webapp): catch possibe errors on request geolocation [`#8640`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8640)
|
||||
- Fix video player in Safari [`#8711`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8711)
|
||||
- fix(webapp): fix property access of possibly undefined objects [`#8639`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8639)
|
||||
- Build source maps [`#8695`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8695)
|
||||
- feat(devops): tool versions [`#8709`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8709)
|
||||
- build(deps-dev): bump eslint-plugin-prettier in /webapp [`#8699`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8699)
|
||||
- build(deps): bump peter-evans/repository-dispatch [`#8701`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8701)
|
||||
- build(deps): bump the metascraper group across 1 directory with 12 updates [`#8572`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8572)
|
||||
- build(deps): bump @aws-sdk/client-s3 from 3.828.0 to 3.832.0 in /backend [`#8708`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8708)
|
||||
- build(deps): bump linkifyjs from 4.2.0 to 4.3.1 in /backend [`#8531`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8531)
|
||||
- build(deps-dev): bump @types/node from 24.0.1 to 24.0.3 in /backend [`#8707`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8707)
|
||||
- build(deps-dev): bump @types/lodash from 4.17.17 to 4.17.18 in /backend [`#8702`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8702)
|
||||
- build(deps-dev): bump the cypress group across 1 directory with 3 updates [`#8698`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8698)
|
||||
- chore(other): set some 'nvm' versions to '24.2.0' [`#8691`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8691)
|
||||
- Put message creation in a transaction with file uploads to avoid empty messages [`#8694`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8694)
|
||||
- fix(webapp): better chat upload ui [`#8693`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8693)
|
||||
|
||||
#### [3.10.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.10.0...3.10.1)
|
||||
|
||||
> 19 June 2025
|
||||
|
||||
- Release v3.10.1 [`#8692`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8692)
|
||||
- feat(backend): logger [`#8655`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8655)
|
||||
- fix(webapp): show hint that message is being saved [`#8690`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8690)
|
||||
- fix(webapp): added timer [`#8658`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/8658)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
DEBUG=true
|
||||
DEBUG=neo4j-graphql-js
|
||||
|
||||
NEO4J_URI=bolt://localhost:7687
|
||||
NEO4J_USERNAME=neo4j
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:24.2.0-alpine AS base
|
||||
FROM node:24.4.0-alpine AS base
|
||||
LABEL org.label-schema.name="ocelot.social:backend"
|
||||
LABEL org.label-schema.description="Backend of the Social Network Software ocelot.social"
|
||||
LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -28,8 +28,8 @@
|
||||
"prod:db:data:categories": "node build/src/db/categories.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.832.0",
|
||||
"@aws-sdk/lib-storage": "^3.828.0",
|
||||
"@aws-sdk/client-s3": "^3.844.0",
|
||||
"@aws-sdk/lib-storage": "^3.842.0",
|
||||
"@sentry/node": "^5.15.4",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"apollo-server": "~2.14.2",
|
||||
@ -38,7 +38,7 @@
|
||||
"body-parser": "^1.20.3",
|
||||
"cheerio": "~1.1.0",
|
||||
"cross-env": "~7.0.3",
|
||||
"dotenv": "~16.5.0",
|
||||
"dotenv": "~17.0.1",
|
||||
"email-templates": "^12.0.3",
|
||||
"express": "^5.1.0",
|
||||
"graphql": "^14.6.0",
|
||||
@ -57,20 +57,20 @@
|
||||
"linkifyjs": "^4.3.1",
|
||||
"lodash": "~4.17.21",
|
||||
"merge-graphql-schemas": "^1.7.8",
|
||||
"metascraper": "^5.47.1",
|
||||
"metascraper-author": "^5.47.1",
|
||||
"metascraper-date": "^5.47.1",
|
||||
"metascraper-description": "^5.47.1",
|
||||
"metascraper-image": "^5.47.1",
|
||||
"metascraper-lang": "^5.47.1",
|
||||
"metascraper": "^5.49.1",
|
||||
"metascraper-author": "^5.49.1",
|
||||
"metascraper-date": "^5.49.1",
|
||||
"metascraper-description": "^5.49.1",
|
||||
"metascraper-image": "^5.49.1",
|
||||
"metascraper-lang": "^5.49.1",
|
||||
"metascraper-lang-detector": "^4.10.2",
|
||||
"metascraper-logo": "^5.47.1",
|
||||
"metascraper-publisher": "^5.47.1",
|
||||
"metascraper-logo": "^5.49.1",
|
||||
"metascraper-publisher": "^5.49.1",
|
||||
"metascraper-soundcloud": "^5.34.4",
|
||||
"metascraper-title": "^5.47.1",
|
||||
"metascraper-url": "^5.47.1",
|
||||
"metascraper-video": "^5.47.1",
|
||||
"metascraper-youtube": "^5.47.1",
|
||||
"metascraper-title": "^5.49.1",
|
||||
"metascraper-url": "^5.49.1",
|
||||
"metascraper-video": "^5.49.1",
|
||||
"metascraper-youtube": "^5.49.1",
|
||||
"migrate": "^2.1.0",
|
||||
"mime-types": "^3.0.1",
|
||||
"minimatch": "^10.0.3",
|
||||
@ -79,7 +79,7 @@
|
||||
"neo4j-graphql-js": "^2.11.5",
|
||||
"neode": "^0.4.9",
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^7.0.3",
|
||||
"nodemailer": "^7.0.5",
|
||||
"nodemailer-html-to-text": "^3.2.0",
|
||||
"preview-email": "^3.1.0",
|
||||
"pug": "^3.0.3",
|
||||
@ -93,11 +93,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
|
||||
"@faker-js/faker": "9.8.0",
|
||||
"@faker-js/faker": "9.9.0",
|
||||
"@types/email-templates": "^10.0.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/lodash": "^4.17.18",
|
||||
"@types/node": "^24.0.3",
|
||||
"@types/jsonwebtoken": "~8.5.1",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/request": "^2.48.12",
|
||||
"@types/slug": "^5.0.9",
|
||||
"@types/uuid": "~9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
@ -106,18 +108,18 @@
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-import-resolver-typescript": "^4.4.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.13.5",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-jsonc": "^2.20.1",
|
||||
"eslint-plugin-n": "^17.20.0",
|
||||
"eslint-plugin-n": "^17.21.0",
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"eslint-plugin-security": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "~3.1.10",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier": "^3.6.2",
|
||||
"require-json5": "^1.3.0",
|
||||
"rosie": "^2.1.1",
|
||||
"ts-jest": "^29.4.0",
|
||||
|
||||
@ -32,12 +32,6 @@ const environment = {
|
||||
LOG_LEVEL: 'DEBUG',
|
||||
}
|
||||
|
||||
const required = {
|
||||
MAPBOX_TOKEN: env.MAPBOX_TOKEN,
|
||||
JWT_SECRET: env.JWT_SECRET,
|
||||
PRIVATE_KEY_PASSPHRASE: env.PRIVATE_KEY_PASSPHRASE,
|
||||
}
|
||||
|
||||
const server = {
|
||||
CLIENT_URI: env.CLIENT_URI ?? 'http://localhost:3000',
|
||||
GRAPHQL_URI: env.GRAPHQL_URI ?? 'http://localhost:4000',
|
||||
@ -97,33 +91,34 @@ const redis = {
|
||||
REDIS_PASSWORD: env.REDIS_PASSWORD,
|
||||
}
|
||||
|
||||
const s3 = {
|
||||
const required = {
|
||||
AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID,
|
||||
AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY,
|
||||
AWS_ENDPOINT: env.AWS_ENDPOINT,
|
||||
AWS_REGION: env.AWS_REGION,
|
||||
AWS_BUCKET: env.AWS_BUCKET,
|
||||
S3_PUBLIC_GATEWAY: env.S3_PUBLIC_GATEWAY,
|
||||
|
||||
MAPBOX_TOKEN: env.MAPBOX_TOKEN,
|
||||
JWT_SECRET: env.JWT_SECRET,
|
||||
PRIVATE_KEY_PASSPHRASE: env.PRIVATE_KEY_PASSPHRASE,
|
||||
}
|
||||
|
||||
export interface S3Configured {
|
||||
AWS_ACCESS_KEY_ID: string
|
||||
AWS_SECRET_ACCESS_KEY: string
|
||||
AWS_ENDPOINT: string
|
||||
AWS_REGION: string
|
||||
AWS_BUCKET: string
|
||||
S3_PUBLIC_GATEWAY: string | undefined
|
||||
const S3_PUBLIC_GATEWAY = env.S3_PUBLIC_GATEWAY
|
||||
|
||||
// https://stackoverflow.com/a/53050575
|
||||
type NoUndefinedField<T> = { [P in keyof T]-?: NoUndefinedField<NonNullable<T[P]>> }
|
||||
|
||||
function assertRequiredConfig(
|
||||
conf: typeof required,
|
||||
): asserts conf is NoUndefinedField<typeof required> {
|
||||
Object.entries(conf).forEach(([key, value]) => {
|
||||
if (!value) {
|
||||
throw new Error(`ERROR: "${key}" env variable is missing.`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const isS3configured = (config: typeof s3): config is S3Configured => {
|
||||
return !!(
|
||||
config.AWS_ACCESS_KEY_ID &&
|
||||
config.AWS_SECRET_ACCESS_KEY &&
|
||||
config.AWS_ENDPOINT &&
|
||||
config.AWS_REGION &&
|
||||
config.AWS_BUCKET
|
||||
)
|
||||
}
|
||||
assertRequiredConfig(required)
|
||||
|
||||
const options = {
|
||||
EMAIL_DEFAULT_SENDER: env.EMAIL_DEFAULT_SENDER,
|
||||
@ -147,24 +142,28 @@ const language = {
|
||||
LANGUAGE_DEFAULT: process.env.LANGUAGE_DEFAULT ?? 'en',
|
||||
}
|
||||
|
||||
// Check if all required configs are present
|
||||
Object.entries(required).map((entry) => {
|
||||
if (!entry[1]) {
|
||||
throw new Error(`ERROR: "${entry[0]}" env variable is missing.`)
|
||||
}
|
||||
return entry
|
||||
})
|
||||
|
||||
export default {
|
||||
const CONFIG = {
|
||||
...environment,
|
||||
...server,
|
||||
...required,
|
||||
...neo4j,
|
||||
...sentry,
|
||||
...redis,
|
||||
...s3,
|
||||
...options,
|
||||
...language,
|
||||
S3_PUBLIC_GATEWAY,
|
||||
}
|
||||
|
||||
export type Config = typeof CONFIG
|
||||
export type S3Config = Pick<
|
||||
Config,
|
||||
| 'AWS_ACCESS_KEY_ID'
|
||||
| 'AWS_SECRET_ACCESS_KEY'
|
||||
| 'AWS_ENDPOINT'
|
||||
| 'AWS_REGION'
|
||||
| 'AWS_BUCKET'
|
||||
| 'S3_PUBLIC_GATEWAY'
|
||||
>
|
||||
export default CONFIG
|
||||
|
||||
export { nodemailerTransportOptions }
|
||||
|
||||
61
backend/src/context/index.ts
Normal file
61
backend/src/context/index.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import pubsubContext from '@context/pubsub'
|
||||
import CONFIG from '@src/config'
|
||||
import type { DecodedUser } from '@src/jwt/decode'
|
||||
import { decode } from '@src/jwt/decode'
|
||||
import ocelotLogger from '@src/logger'
|
||||
import type OcelotLogger from '@src/logger'
|
||||
|
||||
import type { ApolloServerExpressConfig } from 'apollo-server-express'
|
||||
|
||||
const serverDatabase = databaseContext()
|
||||
const serverPubsub = pubsubContext()
|
||||
|
||||
export const getContext =
|
||||
(opts?: {
|
||||
database?: ReturnType<typeof databaseContext>
|
||||
pubsub?: ReturnType<typeof pubsubContext>
|
||||
authenticatedUser: DecodedUser | null | undefined
|
||||
logger?: typeof OcelotLogger
|
||||
config: typeof CONFIG
|
||||
}) =>
|
||||
async (req: { headers: { authorization?: string } }) => {
|
||||
const {
|
||||
database = serverDatabase,
|
||||
pubsub = serverPubsub,
|
||||
authenticatedUser = undefined,
|
||||
logger = ocelotLogger,
|
||||
config = CONFIG,
|
||||
} = opts ?? {}
|
||||
const { driver } = database
|
||||
const user =
|
||||
authenticatedUser === null
|
||||
? null
|
||||
: (authenticatedUser ?? (await decode({ driver, config })(req.headers.authorization)))
|
||||
const result = {
|
||||
database,
|
||||
driver,
|
||||
neode: database.neode,
|
||||
pubsub,
|
||||
logger,
|
||||
user,
|
||||
req,
|
||||
cypherParams: {
|
||||
currentUserId: user ? user.id : null,
|
||||
},
|
||||
config,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const context: ApolloServerExpressConfig['context'] = async (options) => {
|
||||
const { connection, req } = options
|
||||
if (connection) {
|
||||
return connection.context
|
||||
} else {
|
||||
return getContext()(req)
|
||||
}
|
||||
}
|
||||
export type Context = Awaited<ReturnType<ReturnType<typeof getContext>>>
|
||||
@ -13,7 +13,7 @@ import { v4 as uuid } from 'uuid'
|
||||
import { generateInviteCode } from '@graphql/resolvers/inviteCodes'
|
||||
import { isUniqueFor } from '@middleware/sluggifyMiddleware'
|
||||
import uniqueSlug from '@middleware/slugify/uniqueSlug'
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
import { getDriver, getNeode } from './neo4j'
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable n/no-process-exit */
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import sample from 'lodash/sample'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
@ -16,10 +15,9 @@ import { CreateMessage } from '@graphql/queries/CreateMessage'
|
||||
import { createPostMutation } from '@graphql/queries/createPostMutation'
|
||||
import { createRoomMutation } from '@graphql/queries/createRoomMutation'
|
||||
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
|
||||
import createServer from '@src/server'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
|
||||
import Factory from './factories'
|
||||
import { getNeode, getDriver } from './neo4j'
|
||||
import { trophies, verification } from './seed/badges'
|
||||
|
||||
if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
||||
@ -35,22 +33,21 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
console.log('Seeded Data...')
|
||||
|
||||
let authenticatedUser = null
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
|
||||
// locations
|
||||
const context = () => ({
|
||||
authenticatedUser,
|
||||
config: CONFIG,
|
||||
})
|
||||
const { mutate } = createTestClient(server)
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
const { mutate, server, database } = apolloSetup
|
||||
const { neode } = database
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('seed', 'locations')
|
||||
|
||||
// locations
|
||||
const Hamburg = await Factory.build('location', {
|
||||
id: 'region.5127278006398860',
|
||||
name: 'Hamburg',
|
||||
@ -1618,7 +1615,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
throw err
|
||||
} finally {
|
||||
await server.stop()
|
||||
await driver.close()
|
||||
await database.driver.close()
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await neode.close()
|
||||
process.exit(0)
|
||||
|
||||
@ -9,15 +9,14 @@ import { Readable } from 'node:stream'
|
||||
import { S3Client } from '@aws-sdk/client-s3'
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import File from '@db/models/File'
|
||||
import { CreateMessage } from '@graphql/queries/CreateMessage'
|
||||
import { createRoomMutation } from '@graphql/queries/createRoomMutation'
|
||||
import type { S3Configured } from '@src/config'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { S3Config } from '@src/config'
|
||||
|
||||
import { attachments } from './attachments'
|
||||
|
||||
@ -38,7 +37,7 @@ const UploadMock = {
|
||||
|
||||
;(Upload as unknown as jest.Mock).mockImplementation(() => UploadMock)
|
||||
|
||||
const config: S3Configured = {
|
||||
const config: S3Config = {
|
||||
AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID',
|
||||
AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY',
|
||||
AWS_BUCKET: 'AWS_BUCKET',
|
||||
@ -47,20 +46,19 @@ const config: S3Configured = {
|
||||
S3_PUBLIC_GATEWAY: undefined,
|
||||
}
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let authenticatedUser, server, mutate
|
||||
let authenticatedUser
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -115,7 +113,7 @@ describe('delete Attachment', () => {
|
||||
},
|
||||
})
|
||||
|
||||
message = m.data.CreateMessage
|
||||
message = (m.data as any).CreateMessage // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
await database.write({
|
||||
query: `
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import { DeleteObjectCommand, ObjectCannedACL, S3Client } from '@aws-sdk/client-s3'
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
import { UserInputError } from 'apollo-server-express'
|
||||
import slug from 'slug'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { isS3configured, S3Configured } from '@config/index'
|
||||
import type { S3Config } from '@config/index'
|
||||
import { wrapTransaction } from '@graphql/resolvers/images/wrapTransaction'
|
||||
import { s3Service } from '@src/uploads/s3Service'
|
||||
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
import type { Transaction } from 'neo4j-driver'
|
||||
@ -55,22 +54,8 @@ export interface Attachments {
|
||||
) => Promise<any>
|
||||
}
|
||||
|
||||
export const attachments = (config: S3Configured) => {
|
||||
if (!isS3configured(config)) {
|
||||
throw new Error('S3 not configured')
|
||||
}
|
||||
|
||||
const { AWS_BUCKET: Bucket, S3_PUBLIC_GATEWAY } = config
|
||||
|
||||
const { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = config
|
||||
const s3 = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
endpoint: AWS_ENDPOINT,
|
||||
forcePathStyle: true,
|
||||
})
|
||||
export const attachments = (config: S3Config) => {
|
||||
const s3 = s3Service(config, 'attachments')
|
||||
|
||||
const del: Attachments['del'] = async (resource, relationshipType, opts = {}) => {
|
||||
const { transaction } = opts
|
||||
@ -86,17 +71,7 @@ export const attachments = (config: S3Configured) => {
|
||||
)
|
||||
const [file] = txResult.records.map((record) => record.get('fileProps') as File)
|
||||
if (file) {
|
||||
let { pathname } = new URL(file.url, 'http://example.org') // dummy domain to avoid invalid URL error
|
||||
pathname = pathname.substring(1) // remove first character '/'
|
||||
const prefix = `${Bucket}/`
|
||||
if (pathname.startsWith(prefix)) {
|
||||
pathname = pathname.slice(prefix.length)
|
||||
}
|
||||
const params = {
|
||||
Bucket,
|
||||
Key: pathname,
|
||||
}
|
||||
await s3.send(new DeleteObjectCommand(params))
|
||||
await s3.deleteFile(file.url)
|
||||
}
|
||||
return file
|
||||
}
|
||||
@ -119,34 +94,10 @@ export const attachments = (config: S3Configured) => {
|
||||
const { name: fileName, ext } = path.parse(uploadFile.filename)
|
||||
const uniqueFilename = `${uuid()}-${slug(fileName)}${ext}`
|
||||
|
||||
const s3Location = `attachments/${uniqueFilename}`
|
||||
const params = {
|
||||
Bucket,
|
||||
Key: s3Location,
|
||||
ACL: ObjectCannedACL.public_read,
|
||||
ContentType: uploadFile.mimetype,
|
||||
Body: uploadFile.createReadStream(),
|
||||
}
|
||||
const command = new Upload({ client: s3, params })
|
||||
const data = await command.done()
|
||||
let { Location: location } = data
|
||||
if (!location) {
|
||||
throw new Error('File upload did not return `Location`')
|
||||
}
|
||||
|
||||
if (!location.startsWith('https://') && !location.startsWith('http://')) {
|
||||
// Ensure the location has a protocol. Hetzner does not return a protocol in the location.
|
||||
location = `https://${location}`
|
||||
}
|
||||
|
||||
let url = ''
|
||||
if (!S3_PUBLIC_GATEWAY) {
|
||||
url = location
|
||||
} else {
|
||||
const publicLocation = new URL(S3_PUBLIC_GATEWAY)
|
||||
publicLocation.pathname = new URL(location).pathname
|
||||
url = publicLocation.href
|
||||
}
|
||||
const url = await s3.uploadFile({
|
||||
...uploadFile,
|
||||
uniqueFilename,
|
||||
})
|
||||
|
||||
const { name, type } = fileInput
|
||||
const file = { url, name, type, ...fileAttributes }
|
||||
|
||||
@ -1,37 +1,32 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { rewardTrophyBadge } from '@graphql/queries/rewardTrophyBadge'
|
||||
import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let regularUser, administrator, moderator, badge, verification
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
let authenticatedUser
|
||||
let query, mutate
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -838,7 +833,7 @@ describe('Badges', () => {
|
||||
|
||||
describe('check test setup', () => {
|
||||
it('user has one badge and has it selected', async () => {
|
||||
authenticatedUser = regularUser.toJson()
|
||||
authenticatedUser = await regularUser.toJson()
|
||||
const userQuery = gql`
|
||||
{
|
||||
User(id: "regular-user-id") {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
export const defaultTrophyBadge = {
|
||||
id: 'default_trophy',
|
||||
@ -32,7 +32,10 @@ export default {
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
setVerificationBadge: async (_object, args, context, _resolveInfo) => {
|
||||
setVerificationBadge: async (_object, args, context: Context, _resolveInfo) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const {
|
||||
user: { id: currentUserId },
|
||||
} = context
|
||||
@ -70,11 +73,14 @@ export default {
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
|
||||
rewardTrophyBadge: async (_object, args, context: Context, _resolveInfo) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const {
|
||||
user: { id: currentUserId },
|
||||
} = context
|
||||
|
||||
@ -2,29 +2,26 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const database = databaseContext()
|
||||
let variables, commentAuthor, newlyCreatedComment
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
|
||||
|
||||
let server: ApolloServer
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -35,6 +32,7 @@ afterAll(async () => {
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = null
|
||||
variables = {}
|
||||
await database.neode.create('Category', {
|
||||
id: 'cat9',
|
||||
@ -98,14 +96,14 @@ describe('CreateComment', () => {
|
||||
content: "I'm not authorized to comment",
|
||||
}
|
||||
const { errors } = await mutate({ mutation: createCommentMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
const user = await database.neode.create('User', { name: 'Author' })
|
||||
authenticatedUser = await user.toJson()
|
||||
authenticatedUser = (await user.toJson()) as Context['user']
|
||||
})
|
||||
|
||||
describe('given a post', () => {
|
||||
@ -157,7 +155,7 @@ describe('UpdateComment', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: updateCommentMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -169,7 +167,7 @@ describe('UpdateComment', () => {
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: updateCommentMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -208,7 +206,7 @@ describe('UpdateComment', () => {
|
||||
newlyCreatedComment = await newlyCreatedComment.toJson()
|
||||
const {
|
||||
data: { UpdateComment },
|
||||
} = await mutate({ mutation: updateCommentMutation, variables })
|
||||
} = (await mutate({ mutation: updateCommentMutation, variables })) as any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
expect(newlyCreatedComment.updatedAt).toBeTruthy()
|
||||
expect(Date.parse(newlyCreatedComment.updatedAt)).toEqual(expect.any(Number))
|
||||
expect(UpdateComment.updatedAt).toBeTruthy()
|
||||
@ -224,7 +222,7 @@ describe('UpdateComment', () => {
|
||||
it('returns null', async () => {
|
||||
const { data, errors } = await mutate({ mutation: updateCommentMutation, variables })
|
||||
expect(data).toMatchObject({ UpdateComment: null })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -249,7 +247,7 @@ describe('DeleteComment', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const result = await mutate({ mutation: deleteCommentMutation, variables })
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(result.errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -261,7 +259,7 @@ describe('DeleteComment', () => {
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: deleteCommentMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,44 +1,37 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import { createPostMutation } from '@graphql/queries/createPostMutation'
|
||||
import { filterPosts } from '@graphql/queries/filterPosts'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let user
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
})
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
describe('Filter Posts', () => {
|
||||
@ -99,7 +92,7 @@ describe('Filter Posts', () => {
|
||||
it('finds all posts', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({ query: filterPosts() })
|
||||
} = (await query({ query: filterPosts() })) as any
|
||||
expect(result).toHaveLength(4)
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@ -116,7 +109,10 @@ describe('Filter Posts', () => {
|
||||
it('finds the articles', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({ query: filterPosts(), variables: { filter: { postType_in: ['Article'] } } })
|
||||
} = (await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postType_in: ['Article'] } },
|
||||
})) as any
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@ -131,7 +127,10 @@ describe('Filter Posts', () => {
|
||||
it('finds the articles', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({ query: filterPosts(), variables: { filter: { postType_in: ['Event'] } } })
|
||||
} = (await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postType_in: ['Event'] } },
|
||||
})) as any
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@ -146,10 +145,10 @@ describe('Filter Posts', () => {
|
||||
it('finds all posts', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({
|
||||
} = (await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postType_in: ['Article', 'Event'] } },
|
||||
})
|
||||
})) as any
|
||||
expect(result).toHaveLength(4)
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@ -166,10 +165,10 @@ describe('Filter Posts', () => {
|
||||
it('finds the events ordered accordingly', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({
|
||||
} = (await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postType_in: ['Event'] }, orderBy: ['eventStart_desc'] },
|
||||
})
|
||||
})) as any
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
@ -190,10 +189,10 @@ describe('Filter Posts', () => {
|
||||
it('finds the events ordered accordingly', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({
|
||||
} = (await query({
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postType_in: ['Event'] }, orderBy: ['eventStart_asc'] },
|
||||
})
|
||||
})) as any
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
@ -214,7 +213,7 @@ describe('Filter Posts', () => {
|
||||
it('finds only events after given date', async () => {
|
||||
const {
|
||||
data: { Post: result },
|
||||
} = await query({
|
||||
} = (await query({
|
||||
query: filterPosts(),
|
||||
variables: {
|
||||
filter: {
|
||||
@ -226,7 +225,7 @@ describe('Filter Posts', () => {
|
||||
).toISOString(),
|
||||
},
|
||||
},
|
||||
})
|
||||
})) as any
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
|
||||
@ -3,10 +3,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
@ -16,9 +12,11 @@ import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
|
||||
import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
|
||||
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
|
||||
import { updateGroupMutation } from '@graphql/queries/updateGroupMutation'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
// import CONFIG from '@src/config'
|
||||
|
||||
let authenticatedUser
|
||||
let user
|
||||
let noMemberUser
|
||||
let pendingMemberUser
|
||||
@ -27,18 +25,21 @@ let adminMemberUser
|
||||
let ownerMemberUser
|
||||
let secondOwnerMemberUser
|
||||
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const categoryIds = ['cat9', 'cat4', 'cat15']
|
||||
const descriptionAdditional100 =
|
||||
' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789'
|
||||
let variables = {}
|
||||
|
||||
const database = databaseContext()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
const { server } = createServer({ context })
|
||||
const { mutate, query } = createTestClient(server)
|
||||
const config = {
|
||||
CATEGORIES_ACTIVE: true,
|
||||
// MAPBOX_TOKEN: CONFIG.MAPBOX_TOKEN,
|
||||
}
|
||||
|
||||
const seedBasicsAndClearAuthentication = async () => {
|
||||
variables = {}
|
||||
@ -230,7 +231,11 @@ const seedComplexScenarioAndClearAuthentication = async () => {
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -270,7 +275,7 @@ describe('in mode', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: createGroupMutation(), variables })
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -339,17 +344,13 @@ describe('in mode', () => {
|
||||
'<a href="https://domain.org/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789">0</a>',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Description too short!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Description too short!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
})
|
||||
|
||||
describe('with matching amount of categories', () => {
|
||||
it('has new categories', async () => {
|
||||
await expect(
|
||||
@ -382,7 +383,7 @@ describe('in mode', () => {
|
||||
mutation: createGroupMutation(),
|
||||
variables: { ...variables, categoryIds: null },
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Too few categories!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Too few categories!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -392,7 +393,7 @@ describe('in mode', () => {
|
||||
mutation: createGroupMutation(),
|
||||
variables: { ...variables, categoryIds: [] },
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Too few categories!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Too few categories!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -403,7 +404,7 @@ describe('in mode', () => {
|
||||
mutation: createGroupMutation(),
|
||||
variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] },
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Too many categories!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Too many categories!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -581,10 +582,6 @@ describe('in mode', () => {
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
})
|
||||
|
||||
it('has set categories', async () => {
|
||||
await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({
|
||||
data: {
|
||||
@ -811,7 +808,7 @@ describe('in mode', () => {
|
||||
userId: 'current-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1566,7 +1563,7 @@ describe('in mode', () => {
|
||||
roleInGroup: 'pending',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1721,7 +1718,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1747,7 +1744,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1796,7 +1793,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1819,7 +1816,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1842,7 +1839,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1900,7 +1897,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1923,7 +1920,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1940,7 +1937,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1963,7 +1960,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1980,7 +1977,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2003,7 +2000,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2020,7 +2017,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2110,7 +2107,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2127,7 +2124,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2150,7 +2147,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2167,7 +2164,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2190,7 +2187,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2207,7 +2204,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2297,7 +2294,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2320,7 +2317,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2343,7 +2340,7 @@ describe('in mode', () => {
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables,
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2407,7 +2404,7 @@ describe('in mode', () => {
|
||||
userId: 'current-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2524,7 +2521,7 @@ describe('in mode', () => {
|
||||
userId: 'owner-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2538,7 +2535,7 @@ describe('in mode', () => {
|
||||
userId: 'second-owner-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2552,7 +2549,7 @@ describe('in mode', () => {
|
||||
userId: 'none-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2566,7 +2563,7 @@ describe('in mode', () => {
|
||||
userId: 'usual-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2580,7 +2577,7 @@ describe('in mode', () => {
|
||||
userId: 'admin-member-user',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2606,7 +2603,7 @@ describe('in mode', () => {
|
||||
slug: 'my-best-group',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2859,17 +2856,13 @@ describe('in mode', () => {
|
||||
'<a href="https://domain.org/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789">0</a>',
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Description too short!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Description too short!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('categories', () => {
|
||||
beforeEach(async () => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
})
|
||||
|
||||
describe('with matching amount of categories', () => {
|
||||
it('has new categories', async () => {
|
||||
await expect(
|
||||
@ -2906,7 +2899,7 @@ describe('in mode', () => {
|
||||
categoryIds: [],
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Too few categories!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Too few categories!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2920,7 +2913,7 @@ describe('in mode', () => {
|
||||
categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'],
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Too many categories!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Too many categories!')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2940,7 +2933,7 @@ describe('in mode', () => {
|
||||
categoryIds: ['cat4', 'cat27'],
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2958,7 +2951,7 @@ describe('in mode', () => {
|
||||
categoryIds: ['cat4', 'cat27'],
|
||||
},
|
||||
})
|
||||
expect(errors![0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,11 +8,10 @@
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { CATEGORIES_MIN, CATEGORIES_MAX } from '@constants/categories'
|
||||
import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '@constants/groups'
|
||||
import { removeHtmlTags } from '@middleware/helpers/cleanHtml'
|
||||
import type { Context } from '@src/server'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import Resolver, {
|
||||
removeUndefinedNullValuesFromObject,
|
||||
@ -32,6 +31,9 @@ export default {
|
||||
removeUndefinedNullValuesFromObject(matchParams)
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const groupMatchParamsCypher = convertObjectToCypherMapLiteral(matchParams, true)
|
||||
let groupCypher
|
||||
if (isMember === true) {
|
||||
@ -139,13 +141,14 @@ export default {
|
||||
},
|
||||
Mutation: {
|
||||
CreateGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { config } = context
|
||||
const { categoryIds } = params
|
||||
delete params.categoryIds
|
||||
params.locationName = params.locationName === '' ? null : params.locationName
|
||||
if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) {
|
||||
if (config.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) {
|
||||
throw new UserInputError('Too few categories!')
|
||||
}
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length > CATEGORIES_MAX) {
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length > CATEGORIES_MAX) {
|
||||
throw new UserInputError('Too many categories!')
|
||||
}
|
||||
if (
|
||||
@ -158,8 +161,11 @@ export default {
|
||||
params.id = params.id || uuid()
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const categoriesCypher =
|
||||
CONFIG.CATEGORIES_ACTIVE && categoryIds
|
||||
config.CATEGORIES_ACTIVE && categoryIds
|
||||
? `
|
||||
WITH group, membership
|
||||
UNWIND $categoryIds AS categoryId
|
||||
@ -194,7 +200,7 @@ export default {
|
||||
try {
|
||||
const group = await writeTxResultPromise
|
||||
// TODO: put in a middleware, see "UpdateGroup", "UpdateUser"
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session)
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
||||
return group
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
@ -205,13 +211,14 @@ export default {
|
||||
}
|
||||
},
|
||||
UpdateGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { config } = context
|
||||
const { categoryIds } = params
|
||||
delete params.categoryIds
|
||||
const { id: groupId, avatar: avatarInput } = params
|
||||
delete params.avatar
|
||||
params.locationName = params.locationName === '' ? null : params.locationName
|
||||
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds) {
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds) {
|
||||
if (categoryIds.length < CATEGORIES_MIN) {
|
||||
throw new UserInputError('Too few categories!')
|
||||
}
|
||||
@ -226,7 +233,7 @@ export default {
|
||||
throw new UserInputError('Description too short!')
|
||||
}
|
||||
const session = context.driver.session()
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
@ -237,13 +244,16 @@ export default {
|
||||
})
|
||||
}
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
let updateGroupCypher = `
|
||||
MATCH (group:Group {id: $groupId})
|
||||
SET group += $params
|
||||
SET group.updatedAt = toString(datetime())
|
||||
WITH group
|
||||
`
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
updateGroupCypher += `
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
@ -263,14 +273,16 @@ export default {
|
||||
})
|
||||
const [group] = transactionResponse.records.map((record) => record.get('group'))
|
||||
if (avatarInput) {
|
||||
await images.mergeImage(group, 'AVATAR_IMAGE', avatarInput, { transaction })
|
||||
await images(context.config).mergeImage(group, 'AVATAR_IMAGE', avatarInput, {
|
||||
transaction,
|
||||
})
|
||||
}
|
||||
return group
|
||||
})
|
||||
try {
|
||||
const group = await writeTxResultPromise
|
||||
// TODO: put in a middleware, see "CreateGroup", "UpdateUser"
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session)
|
||||
await createOrUpdateLocations('Group', params.id, params.locationName, session, context)
|
||||
return group
|
||||
} catch (error) {
|
||||
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
@ -380,10 +392,16 @@ export default {
|
||||
}
|
||||
},
|
||||
muteGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const { groupId } = params
|
||||
const userId = context.user.id
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const transactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (group:Group { id: $groupId })
|
||||
@ -409,6 +427,9 @@ export default {
|
||||
}
|
||||
},
|
||||
unmuteGroup: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const { groupId } = params
|
||||
const userId = context.user.id
|
||||
const session = context.driver.session()
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import normalizeEmail from './normalizeEmail'
|
||||
|
||||
export default async function createPasswordReset(options) {
|
||||
export default async function createPasswordReset(options: {
|
||||
driver: Context['driver']
|
||||
nonce: string
|
||||
email: string
|
||||
issuedAt?: Date
|
||||
}) {
|
||||
const { driver, nonce, email, issuedAt = new Date() } = options
|
||||
const normalizedEmail = normalizeEmail(email)
|
||||
const session = driver.session()
|
||||
@ -33,6 +38,6 @@ export default async function createPasswordReset(options) {
|
||||
const [records] = await createPasswordResetTxPromise
|
||||
return records || {}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
import CONFIG, { isS3configured } from '@config/index'
|
||||
import type { Context } from '@src/context'
|
||||
import type { FileDeleteCallback, FileUploadCallback } from '@src/uploads/types'
|
||||
|
||||
import { images as imagesLocal } from './imagesLocal'
|
||||
import { images as imagesS3 } from './imagesS3'
|
||||
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
import type { Transaction } from 'neo4j-driver'
|
||||
|
||||
export type FileDeleteCallback = (url: string) => Promise<void>
|
||||
|
||||
export type FileUploadCallback = (
|
||||
upload: Pick<FileUpload, 'createReadStream' | 'mimetype'> & { uniqueFilename: string },
|
||||
) => Promise<string>
|
||||
export interface DeleteImageOpts {
|
||||
transaction?: Transaction
|
||||
deleteCallback?: FileDeleteCallback
|
||||
@ -55,4 +50,4 @@ export interface Images {
|
||||
) => Promise<any>
|
||||
}
|
||||
|
||||
export const images = isS3configured(CONFIG) ? imagesS3(CONFIG) : imagesLocal
|
||||
export const images = (config: Context['config']) => imagesS3(config)
|
||||
|
||||
@ -1,364 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
|
||||
import { images } from './imagesLocal'
|
||||
|
||||
import type { ImageInput } from './images'
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
const uuid = '[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}'
|
||||
let uploadCallback
|
||||
let deleteCallback
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
uploadCallback = jest.fn(({ uniqueFilename }) => `/uploads/${uniqueFilename}`)
|
||||
deleteCallback = jest.fn()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
afterEach(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('deleteImage', () => {
|
||||
const { deleteImage } = images
|
||||
|
||||
describe('given a resource with an image', () => {
|
||||
let user: { id: string }
|
||||
beforeEach(async () => {
|
||||
const u = await Factory.build(
|
||||
'user',
|
||||
{},
|
||||
{
|
||||
avatar: Factory.build('image', {
|
||||
url: 'http://localhost/some/avatar/url/',
|
||||
alt: 'This is the avatar image of a user',
|
||||
}),
|
||||
},
|
||||
)
|
||||
user = await u.toJson()
|
||||
})
|
||||
|
||||
it('deletes `Image` node', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
await deleteImage(user, 'AVATAR_IMAGE', { deleteCallback })
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
})
|
||||
|
||||
it('calls deleteCallback', async () => {
|
||||
const u = await Factory.build('user')
|
||||
user = await u.toJson()
|
||||
await deleteImage(user, 'AVATAR_IMAGE', { deleteCallback })
|
||||
expect(deleteCallback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('given a transaction parameter', () => {
|
||||
it('executes cypher statements within the transaction', async () => {
|
||||
const session = driver.session()
|
||||
let someString: string
|
||||
try {
|
||||
someString = await session.writeTransaction(async (transaction) => {
|
||||
await deleteImage(user, 'AVATAR_IMAGE', {
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
const txResult = await transaction.run('RETURN "Hello" as result')
|
||||
const [result] = txResult.records.map((record) => record.get('result'))
|
||||
return result
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
expect(someString).toEqual('Hello')
|
||||
})
|
||||
|
||||
it('rolls back the transaction in case of errors', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
const session = driver.session()
|
||||
try {
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
await deleteImage(user, 'AVATAR_IMAGE', {
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
throw new Error('Ouch!')
|
||||
})
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
// nothing has been deleted
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
// all good
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mergeImage', () => {
|
||||
const { mergeImage } = images
|
||||
let imageInput: ImageInput
|
||||
let post: { id: string }
|
||||
beforeEach(() => {
|
||||
imageInput = {
|
||||
alt: 'A description of the new image',
|
||||
}
|
||||
})
|
||||
|
||||
describe('given image.upload', () => {
|
||||
beforeEach(() => {
|
||||
const createReadStream: FileUpload['createReadStream'] = (() => ({
|
||||
pipe: () => ({
|
||||
on: (_, callback) => callback(),
|
||||
}),
|
||||
})) as unknown as FileUpload['createReadStream']
|
||||
imageInput = {
|
||||
...imageInput,
|
||||
upload: Promise.resolve({
|
||||
filename: 'image.jpg',
|
||||
mimetype: 'image/jpeg',
|
||||
encoding: '7bit',
|
||||
createReadStream,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
describe('on existing resource', () => {
|
||||
beforeEach(async () => {
|
||||
const p = await Factory.build(
|
||||
'post',
|
||||
{ id: 'p99' },
|
||||
{
|
||||
author: Factory.build('user', {}, { avatar: null }),
|
||||
image: null,
|
||||
},
|
||||
)
|
||||
post = await p.toJson()
|
||||
})
|
||||
|
||||
it('returns new image', async () => {
|
||||
await expect(
|
||||
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
|
||||
).resolves.toMatchObject({
|
||||
url: expect.any(String),
|
||||
alt: 'A description of the new image',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls upload callback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(uploadCallback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('creates `:Image` node', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
})
|
||||
|
||||
it('creates a url safe name', async () => {
|
||||
if (!imageInput.upload) {
|
||||
throw new Error('Test imageInput was not setup correctly.')
|
||||
}
|
||||
const upload = await imageInput.upload
|
||||
upload.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg'
|
||||
imageInput.upload = Promise.resolve(upload)
|
||||
await expect(
|
||||
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
|
||||
).resolves.toMatchObject({
|
||||
url: expect.stringMatching(new RegExp(`^/uploads/${uuid}-foo-bar-avatar.jpg`)),
|
||||
})
|
||||
})
|
||||
|
||||
it('connects resource with image via given image type', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
const result = await neode.cypher(
|
||||
`MATCH(p:Post {id: "p99"})-[:HERO_IMAGE]->(i:Image) RETURN i,p`,
|
||||
{},
|
||||
)
|
||||
post = neode.hydrateFirst<{ id: string }>(result, 'p', neode.model('Post')).properties()
|
||||
const image = neode.hydrateFirst(result, 'i', neode.model('Image'))
|
||||
expect(post).toBeTruthy()
|
||||
expect(image).toBeTruthy()
|
||||
})
|
||||
|
||||
it('sets metadata', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
const image = await neode.first<typeof Image>('Image', {}, undefined)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'A description of the new image',
|
||||
createdAt: expect.any(String),
|
||||
url: expect.any(String),
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a transaction parameter', () => {
|
||||
it('executes cypher statements within the transaction', async () => {
|
||||
const session = driver.session()
|
||||
try {
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
const image = await mergeImage(post, 'HERO_IMAGE', imageInput, {
|
||||
uploadCallback,
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
return transaction.run(
|
||||
`
|
||||
MATCH(image:Image {url: $image.url})
|
||||
SET image.alt = 'This alt text gets overwritten'
|
||||
RETURN image {.*}
|
||||
`,
|
||||
{ image },
|
||||
)
|
||||
})
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
const image = await neode.first<typeof Image>(
|
||||
'Image',
|
||||
{ alt: 'This alt text gets overwritten' },
|
||||
undefined,
|
||||
)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'This alt text gets overwritten',
|
||||
})
|
||||
})
|
||||
|
||||
it('rolls back the transaction in case of errors', async () => {
|
||||
const session = driver.session()
|
||||
try {
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
const image = await mergeImage(post, 'HERO_IMAGE', imageInput, {
|
||||
uploadCallback,
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
return transaction.run('Ooops invalid cypher!', { image })
|
||||
})
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
// nothing has been created
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
// all good
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('if resource has an image already', () => {
|
||||
beforeEach(async () => {
|
||||
const [post, image] = await Promise.all([
|
||||
neode.find('Post', 'p99'),
|
||||
Factory.build('image'),
|
||||
])
|
||||
await post.relateTo(image, 'image')
|
||||
})
|
||||
|
||||
it('calls deleteCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(deleteCallback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('calls uploadCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(uploadCallback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('updates metadata of existing image node', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
const image = await neode.first<typeof Image>('Image', {}, undefined)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'A description of the new image',
|
||||
createdAt: expect.any(String),
|
||||
url: expect.any(String),
|
||||
// TODO
|
||||
// width:
|
||||
// height:
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('without image.upload', () => {
|
||||
it('throws UserInputError', async () => {
|
||||
const p = await Factory.build('post', { id: 'p99' }, { image: null })
|
||||
post = await p.toJson()
|
||||
await expect(mergeImage(post, 'HERO_IMAGE', imageInput)).rejects.toEqual(
|
||||
new UserInputError('Cannot find image for given resource'),
|
||||
)
|
||||
})
|
||||
|
||||
describe('if resource has an image already', () => {
|
||||
beforeEach(async () => {
|
||||
const p = await Factory.build(
|
||||
'post',
|
||||
{
|
||||
id: 'p99',
|
||||
},
|
||||
{
|
||||
author: Factory.build(
|
||||
'user',
|
||||
{},
|
||||
{
|
||||
avatar: null,
|
||||
},
|
||||
),
|
||||
image: Factory.build('image', {
|
||||
alt: 'This is the previous, not updated image',
|
||||
url: 'http://localhost/some/original/url',
|
||||
}),
|
||||
},
|
||||
)
|
||||
post = await p.toJson()
|
||||
})
|
||||
|
||||
it('does not call deleteCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(deleteCallback).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not call uploadCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(uploadCallback).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('updates metadata', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
const images = await neode.all('Image')
|
||||
expect(images).toHaveLength(1)
|
||||
await expect(images.first().toJson()).resolves.toMatchObject({
|
||||
createdAt: expect.any(String),
|
||||
url: expect.any(String),
|
||||
alt: 'A description of the new image',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,131 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable promise/avoid-new */
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
|
||||
import { existsSync, unlinkSync, createWriteStream } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import slug from 'slug'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { wrapTransaction } from './wrapTransaction'
|
||||
|
||||
import type { Images, FileDeleteCallback, FileUploadCallback } from './images'
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
|
||||
const deleteImage: Images['deleteImage'] = async (resource, relationshipType, opts = {}) => {
|
||||
const { transaction, deleteCallback } = opts
|
||||
if (!transaction) return wrapTransaction(deleteImage, [resource, relationshipType], opts)
|
||||
const txResult = await transaction.run(
|
||||
`
|
||||
MATCH (resource {id: $resource.id})-[rel:${relationshipType}]->(image:Image)
|
||||
WITH image, image {.*} as imageProps
|
||||
DETACH DELETE image
|
||||
RETURN imageProps
|
||||
`,
|
||||
{ resource },
|
||||
)
|
||||
const [image] = txResult.records.map((record) => record.get('imageProps'))
|
||||
// This behaviour differs from `mergeImage`. If you call `mergeImage`
|
||||
// with metadata for an image that does not exist, it's an indicator
|
||||
// of an error (so throw an error). If we bulk delete an image, it
|
||||
// could very well be that there is no image for the resource.
|
||||
if (image) deleteImageFile(image, deleteCallback)
|
||||
return image
|
||||
}
|
||||
|
||||
const mergeImage: Images['mergeImage'] = async (
|
||||
resource,
|
||||
relationshipType,
|
||||
imageInput,
|
||||
opts = {},
|
||||
) => {
|
||||
if (typeof imageInput === 'undefined') return
|
||||
if (imageInput === null) return deleteImage(resource, relationshipType, opts)
|
||||
const { transaction, uploadCallback, deleteCallback } = opts
|
||||
if (!transaction)
|
||||
return wrapTransaction(mergeImage, [resource, relationshipType, imageInput], opts)
|
||||
|
||||
let txResult
|
||||
txResult = await transaction.run(
|
||||
`
|
||||
MATCH (resource {id: $resource.id})-[:${relationshipType}]->(image:Image)
|
||||
RETURN image {.*}
|
||||
`,
|
||||
{ resource },
|
||||
)
|
||||
const [existingImage] = txResult.records.map((record) => record.get('image'))
|
||||
const { upload } = imageInput
|
||||
if (!(existingImage || upload)) throw new UserInputError('Cannot find image for given resource')
|
||||
if (existingImage && upload) deleteImageFile(existingImage, deleteCallback)
|
||||
const url = await uploadImageFile(upload, uploadCallback)
|
||||
const { alt, sensitive, aspectRatio, type } = imageInput
|
||||
const image = { alt, sensitive, aspectRatio, url, type }
|
||||
txResult = await transaction.run(
|
||||
`
|
||||
MATCH (resource {id: $resource.id})
|
||||
MERGE (resource)-[:${relationshipType}]->(image:Image)
|
||||
ON CREATE SET image.createdAt = toString(datetime())
|
||||
ON MATCH SET image.updatedAt = toString(datetime())
|
||||
SET image += $image
|
||||
RETURN image {.*}
|
||||
`,
|
||||
{ resource, image },
|
||||
)
|
||||
const [mergedImage] = txResult.records.map((record) => record.get('image'))
|
||||
return mergedImage
|
||||
}
|
||||
|
||||
const localFileDelete: FileDeleteCallback = async (url) => {
|
||||
const location = `public${url}`
|
||||
// eslint-disable-next-line n/no-sync
|
||||
if (existsSync(location)) unlinkSync(location)
|
||||
}
|
||||
|
||||
const deleteImageFile = (image, deleteCallback: FileDeleteCallback | undefined) => {
|
||||
if (!deleteCallback) {
|
||||
deleteCallback = localFileDelete
|
||||
}
|
||||
const { url } = image
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
deleteCallback(url)
|
||||
return url
|
||||
}
|
||||
|
||||
const uploadImageFile = async (
|
||||
upload: Promise<FileUpload> | undefined,
|
||||
uploadCallback: FileUploadCallback | undefined,
|
||||
) => {
|
||||
if (!upload) return undefined
|
||||
if (!uploadCallback) {
|
||||
uploadCallback = localFileUpload
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { createReadStream, filename, mimetype } = await upload
|
||||
const { name, ext } = path.parse(filename)
|
||||
const uniqueFilename = `${uuid()}-${slug(name)}${ext}`
|
||||
return uploadCallback({ createReadStream, uniqueFilename, mimetype })
|
||||
}
|
||||
|
||||
const localFileUpload: FileUploadCallback = ({ createReadStream, uniqueFilename }) => {
|
||||
const destination = `/uploads/${uniqueFilename}`
|
||||
return new Promise((resolve, reject) =>
|
||||
createReadStream().pipe(
|
||||
createWriteStream(`public${destination}`)
|
||||
.on('finish', () => resolve(destination))
|
||||
.on('error', (error) => reject(error)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const images: Images = {
|
||||
deleteImage,
|
||||
mergeImage,
|
||||
}
|
||||
@ -1,29 +1,47 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
import { DeleteObjectCommand } from '@aws-sdk/client-s3'
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import type { S3Configured } from '@src/config'
|
||||
import type { S3Config } from '@src/config'
|
||||
|
||||
import { images } from './imagesS3'
|
||||
|
||||
import type { ImageInput } from './images'
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
|
||||
jest.mock('@aws-sdk/client-s3', () => {
|
||||
return {
|
||||
S3Client: jest.fn().mockImplementation(() => ({
|
||||
send: jest.fn(),
|
||||
})),
|
||||
ObjectCannedACL: { public_read: 'public_read' },
|
||||
DeleteObjectCommand: jest.fn().mockImplementation(() => ({})),
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('@aws-sdk/lib-storage', () => {
|
||||
return {
|
||||
Upload: jest.fn().mockImplementation(({ params: { Key } }: { params: { Key: string } }) => ({
|
||||
done: () => Promise.resolve({ Location: `http://your-objectstorage.com/bucket/${Key}` }),
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
const mockUpload = jest.mocked(Upload)
|
||||
const mockDeleteObjectCommand = jest.mocked(DeleteObjectCommand)
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
const uuid = '[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}'
|
||||
let uploadCallback
|
||||
let deleteCallback
|
||||
|
||||
const config: S3Configured = {
|
||||
const config: S3Config = {
|
||||
AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID',
|
||||
AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY',
|
||||
AWS_BUCKET: 'AWS_BUCKET',
|
||||
@ -41,16 +59,10 @@ afterAll(async () => {
|
||||
await driver.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
uploadCallback = jest.fn(
|
||||
({ uniqueFilename }) => `http://your-objectstorage.com/bucket/${uniqueFilename}`,
|
||||
)
|
||||
deleteCallback = jest.fn()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
afterEach(async () => {
|
||||
await cleanDatabase()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('deleteImage', () => {
|
||||
@ -73,13 +85,13 @@ describe('deleteImage', () => {
|
||||
|
||||
it('deletes `Image` node', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
await deleteImage(user, 'AVATAR_IMAGE', { deleteCallback })
|
||||
await deleteImage(user, 'AVATAR_IMAGE')
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
})
|
||||
|
||||
it('calls deleteCallback', async () => {
|
||||
await deleteImage(user, 'AVATAR_IMAGE', { deleteCallback })
|
||||
expect(deleteCallback).toHaveBeenCalled()
|
||||
await deleteImage(user, 'AVATAR_IMAGE')
|
||||
expect(mockDeleteObjectCommand).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('given a transaction parameter', () => {
|
||||
@ -89,7 +101,6 @@ describe('deleteImage', () => {
|
||||
try {
|
||||
someString = await session.writeTransaction(async (transaction) => {
|
||||
await deleteImage(user, 'AVATAR_IMAGE', {
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
const txResult = await transaction.run('RETURN "Hello" as result')
|
||||
@ -109,7 +120,6 @@ describe('deleteImage', () => {
|
||||
try {
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
await deleteImage(user, 'AVATAR_IMAGE', {
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
throw new Error('Ouch!')
|
||||
@ -169,22 +179,20 @@ describe('mergeImage', () => {
|
||||
})
|
||||
|
||||
it('returns new image', async () => {
|
||||
await expect(
|
||||
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
|
||||
).resolves.toMatchObject({
|
||||
await expect(mergeImage(post, 'HERO_IMAGE', imageInput)).resolves.toMatchObject({
|
||||
url: expect.any(String),
|
||||
alt: 'A description of the new image',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls upload callback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(uploadCallback).toHaveBeenCalled()
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
expect(mockUpload).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('creates `:Image` node', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(0)
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
})
|
||||
|
||||
@ -195,11 +203,9 @@ describe('mergeImage', () => {
|
||||
const upload = await imageInput.upload
|
||||
upload.filename = '/path/to/awkward?/ file-location/?foo- bar-avatar.jpg'
|
||||
imageInput.upload = Promise.resolve(upload)
|
||||
await expect(
|
||||
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
|
||||
).resolves.toMatchObject({
|
||||
await expect(mergeImage(post, 'HERO_IMAGE', imageInput)).resolves.toMatchObject({
|
||||
url: expect.stringMatching(
|
||||
new RegExp(`^http://your-objectstorage.com/bucket/${uuid}-foo-bar-avatar.jpg`),
|
||||
new RegExp(`^http://your-objectstorage.com/bucket/original/${uuid}-foo-bar-avatar.jpg`),
|
||||
),
|
||||
})
|
||||
})
|
||||
@ -217,18 +223,18 @@ describe('mergeImage', () => {
|
||||
const upload = await imageInput.upload
|
||||
upload.filename = '/path/to/file-location/foo-bar-avatar.jpg'
|
||||
imageInput.upload = Promise.resolve(upload)
|
||||
await expect(
|
||||
mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }),
|
||||
).resolves.toMatchObject({
|
||||
await expect(mergeImage(post, 'HERO_IMAGE', imageInput)).resolves.toMatchObject({
|
||||
url: expect.stringMatching(
|
||||
new RegExp(`^http://s3-public-gateway.com/bucket/${uuid}-foo-bar-avatar.jpg`),
|
||||
new RegExp(
|
||||
`^http://s3-public-gateway.com/bucket/original/${uuid}-foo-bar-avatar.jpg`,
|
||||
),
|
||||
),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('connects resource with image via given image type', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
const result = await neode.cypher(
|
||||
`MATCH(p:Post {id: "p99"})-[:HERO_IMAGE]->(i:Image) RETURN i,p`,
|
||||
{},
|
||||
@ -240,7 +246,7 @@ describe('mergeImage', () => {
|
||||
})
|
||||
|
||||
it('sets metadata', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
const image = await neode.first<typeof Image>('Image', {}, undefined)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
alt: 'A description of the new image',
|
||||
@ -255,8 +261,6 @@ describe('mergeImage', () => {
|
||||
try {
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
const image = await mergeImage(post, 'HERO_IMAGE', imageInput, {
|
||||
uploadCallback,
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
return transaction.run(
|
||||
@ -286,8 +290,6 @@ describe('mergeImage', () => {
|
||||
try {
|
||||
await session.writeTransaction(async (transaction) => {
|
||||
const image = await mergeImage(post, 'HERO_IMAGE', imageInput, {
|
||||
uploadCallback,
|
||||
deleteCallback,
|
||||
transaction,
|
||||
})
|
||||
return transaction.run('Ooops invalid cypher!', { image })
|
||||
@ -313,18 +315,18 @@ describe('mergeImage', () => {
|
||||
})
|
||||
|
||||
it('calls deleteCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(deleteCallback).toHaveBeenCalled()
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
expect(mockDeleteObjectCommand).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('calls uploadCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(uploadCallback).toHaveBeenCalled()
|
||||
it('calls Upload', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
expect(mockUpload).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('updates metadata of existing image node', async () => {
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
await expect(neode.all('Image')).resolves.toHaveLength(1)
|
||||
const image = await neode.first<typeof Image>('Image', {}, undefined)
|
||||
await expect(image.toJson()).resolves.toMatchObject({
|
||||
@ -374,17 +376,17 @@ describe('mergeImage', () => {
|
||||
})
|
||||
|
||||
it('does not call deleteCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(deleteCallback).not.toHaveBeenCalled()
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
expect(mockDeleteObjectCommand).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not call uploadCallback', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
expect(uploadCallback).not.toHaveBeenCalled()
|
||||
it('does not call Upload', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
expect(mockUpload).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('updates metadata', async () => {
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback })
|
||||
await mergeImage(post, 'HERO_IMAGE', imageInput)
|
||||
const images = await neode.all('Image')
|
||||
expect(images).toHaveLength(1)
|
||||
await expect(images.first().toJson()).resolves.toMatchObject({
|
||||
|
||||
@ -1,34 +1,22 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import { S3Client, DeleteObjectCommand, ObjectCannedACL } from '@aws-sdk/client-s3'
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import { FileUpload } from 'graphql-upload'
|
||||
import slug from 'slug'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { S3Configured } from '@config/index'
|
||||
import type { S3Config } from '@config/index'
|
||||
import { s3Service } from '@src/uploads/s3Service'
|
||||
|
||||
import { wrapTransaction } from './wrapTransaction'
|
||||
|
||||
import type { Image, Images, FileDeleteCallback, FileUploadCallback } from './images'
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
import type { Image, Images } from './images'
|
||||
|
||||
export const images = (config: S3Configured) => {
|
||||
// const widths = [34, 160, 320, 640, 1024]
|
||||
const { AWS_BUCKET: Bucket, S3_PUBLIC_GATEWAY } = config
|
||||
|
||||
const { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = config
|
||||
const s3 = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
endpoint: AWS_ENDPOINT,
|
||||
forcePathStyle: true,
|
||||
})
|
||||
export const images = (config: S3Config) => {
|
||||
const s3 = s3Service(config, 'original')
|
||||
|
||||
const deleteImage: Images['deleteImage'] = async (resource, relationshipType, opts = {}) => {
|
||||
const { transaction, deleteCallback = s3Delete } = opts
|
||||
const { transaction } = opts
|
||||
if (!transaction) return wrapTransaction(deleteImage, [resource, relationshipType], opts)
|
||||
const txResult = await transaction.run(
|
||||
`
|
||||
@ -45,7 +33,7 @@ export const images = (config: S3Configured) => {
|
||||
// of an error (so throw an error). If we bulk delete an image, it
|
||||
// could very well be that there is no image for the resource.
|
||||
if (image) {
|
||||
await deleteCallback(image.url)
|
||||
await s3.deleteFile(image.url)
|
||||
}
|
||||
return image
|
||||
}
|
||||
@ -58,7 +46,7 @@ export const images = (config: S3Configured) => {
|
||||
) => {
|
||||
if (typeof imageInput === 'undefined') return
|
||||
if (imageInput === null) return deleteImage(resource, relationshipType, opts)
|
||||
const { transaction, uploadCallback, deleteCallback = s3Delete } = opts
|
||||
const { transaction } = opts
|
||||
if (!transaction)
|
||||
return wrapTransaction(mergeImage, [resource, relationshipType, imageInput], opts)
|
||||
|
||||
@ -73,9 +61,9 @@ export const images = (config: S3Configured) => {
|
||||
const { upload } = imageInput
|
||||
if (!(existingImage || upload)) throw new UserInputError('Cannot find image for given resource')
|
||||
if (existingImage && upload) {
|
||||
await deleteCallback(existingImage.url)
|
||||
await s3.deleteFile(existingImage.url)
|
||||
}
|
||||
const url = await uploadImageFile(upload, uploadCallback)
|
||||
const url = await uploadImageFile(upload)
|
||||
const { alt, sensitive, aspectRatio, type } = imageInput
|
||||
const image = { alt, sensitive, aspectRatio, url, type }
|
||||
txResult = await transaction.run(
|
||||
@ -93,58 +81,17 @@ export const images = (config: S3Configured) => {
|
||||
return mergedImage
|
||||
}
|
||||
|
||||
const uploadImageFile = async (
|
||||
uploadPromise: Promise<FileUpload> | undefined,
|
||||
uploadCallback: FileUploadCallback | undefined = s3Upload,
|
||||
) => {
|
||||
const uploadImageFile = async (uploadPromise: Promise<FileUpload> | undefined) => {
|
||||
if (!uploadPromise) return undefined
|
||||
const upload = await uploadPromise
|
||||
const { name, ext } = path.parse(upload.filename)
|
||||
const uniqueFilename = `${uuid()}-${slug(name)}${ext}`
|
||||
const Location = await uploadCallback({ ...upload, uniqueFilename })
|
||||
if (!S3_PUBLIC_GATEWAY) {
|
||||
return Location
|
||||
}
|
||||
const publicLocation = new URL(S3_PUBLIC_GATEWAY)
|
||||
publicLocation.pathname = new URL(Location).pathname
|
||||
return publicLocation.href
|
||||
return await s3.uploadFile({ ...upload, uniqueFilename })
|
||||
}
|
||||
|
||||
const s3Upload: FileUploadCallback = async ({ createReadStream, uniqueFilename, mimetype }) => {
|
||||
const s3Location = `original/${uniqueFilename}`
|
||||
const params = {
|
||||
Bucket,
|
||||
Key: s3Location,
|
||||
ACL: ObjectCannedACL.public_read,
|
||||
ContentType: mimetype,
|
||||
Body: createReadStream(),
|
||||
}
|
||||
const command = new Upload({ client: s3, params })
|
||||
const data = await command.done()
|
||||
const { Location } = data
|
||||
if (!Location) {
|
||||
throw new Error('File upload did not return `Location`')
|
||||
}
|
||||
return Location
|
||||
}
|
||||
|
||||
const s3Delete: FileDeleteCallback = async (url) => {
|
||||
let { pathname } = new URL(url, 'http://example.org') // dummy domain to avoid invalid URL error
|
||||
pathname = pathname.substring(1) // remove first character '/'
|
||||
const prefix = `${Bucket}/`
|
||||
if (pathname.startsWith(prefix)) {
|
||||
pathname = pathname.slice(prefix.length)
|
||||
}
|
||||
const params = {
|
||||
Bucket,
|
||||
Key: pathname,
|
||||
}
|
||||
await s3.send(new DeleteObjectCommand(params))
|
||||
}
|
||||
|
||||
const images: Images = {
|
||||
const images = {
|
||||
deleteImage,
|
||||
mergeImage,
|
||||
}
|
||||
} satisfies Images
|
||||
return images
|
||||
}
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
import { currentUser } from '@graphql/queries/currentUser'
|
||||
@ -20,26 +16,24 @@ import {
|
||||
authenticatedValidateInviteCode,
|
||||
unauthenticatedValidateInviteCode,
|
||||
} from '@graphql/queries/validateInviteCode'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup, TEST_CONFIG } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
let authenticatedUser
|
||||
let query, mutate
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -479,7 +473,7 @@ describe('generatePersonalInviteCode', () => {
|
||||
|
||||
it('throws an error when the max amount of invite links was reached', async () => {
|
||||
let lastCode
|
||||
for (let i = 0; i < CONFIG.INVITE_CODES_PERSONAL_PER_USER; i++) {
|
||||
for (let i = 0; i < TEST_CONFIG.INVITE_CODES_PERSONAL_PER_USER; i++) {
|
||||
lastCode = await mutate({ mutation: generatePersonalInviteCode })
|
||||
expect(lastCode).toMatchObject({
|
||||
errors: undefined,
|
||||
@ -740,7 +734,7 @@ describe('generateGroupInviteCode', () => {
|
||||
|
||||
it('throws an error when the max amount of invite links was reached', async () => {
|
||||
let lastCode
|
||||
for (let i = 0; i < CONFIG.INVITE_CODES_GROUP_PER_USER; i++) {
|
||||
for (let i = 0; i < TEST_CONFIG.INVITE_CODES_GROUP_PER_USER; i++) {
|
||||
lastCode = await mutate({
|
||||
mutation: generateGroupInviteCode,
|
||||
variables: { groupId: 'public-group' },
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import CONFIG from '@config/index'
|
||||
import registrationConstants from '@constants/registrationBranded'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
@ -53,6 +51,9 @@ export const validateInviteCode = async (context: Context, inviteCode) => {
|
||||
}
|
||||
|
||||
export const redeemInviteCode = async (context: Context, code, newUser = false) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const result = (
|
||||
await context.database.query({
|
||||
query: `
|
||||
@ -159,7 +160,9 @@ export default {
|
||||
})
|
||||
).records[0].get('count')
|
||||
|
||||
if (parseInt(userInviteCodeAmount as string) >= CONFIG.INVITE_CODES_PERSONAL_PER_USER) {
|
||||
if (
|
||||
parseInt(userInviteCodeAmount as string) >= context.config.INVITE_CODES_PERSONAL_PER_USER
|
||||
) {
|
||||
throw new Error('You have reached the maximum of Invite Codes you can generate')
|
||||
}
|
||||
|
||||
@ -198,7 +201,7 @@ export default {
|
||||
})
|
||||
).records[0].get('count')
|
||||
|
||||
if (parseInt(userInviteCodeAmount as string) >= CONFIG.INVITE_CODES_GROUP_PER_USER) {
|
||||
if (parseInt(userInviteCodeAmount as string) >= context.config.INVITE_CODES_GROUP_PER_USER) {
|
||||
throw new Error(
|
||||
'You have reached the maximum of Invite Codes you can generate for this group',
|
||||
)
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import Resolver from './helpers/Resolver'
|
||||
import { queryLocations } from './users/location'
|
||||
|
||||
@ -23,7 +24,7 @@ export default {
|
||||
'nameRU',
|
||||
],
|
||||
}),
|
||||
distanceToMe: async (parent, _params, context, _resolveInfo) => {
|
||||
distanceToMe: async (parent, _params, context: Context, _resolveInfo) => {
|
||||
if (!parent.id) {
|
||||
throw new Error('Can not identify selected Location!')
|
||||
}
|
||||
@ -53,9 +54,9 @@ export default {
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
queryLocations: async (_object, args, _context, _resolveInfo) => {
|
||||
queryLocations: async (_object, args, context: Context, _resolveInfo) => {
|
||||
try {
|
||||
return queryLocations(args)
|
||||
return queryLocations(args, context)
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
|
||||
@ -5,11 +5,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { Readable } from 'node:stream'
|
||||
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { Upload } from 'graphql-upload/public/index'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import pubsubContext from '@context/pubsub'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { CreateMessage } from '@graphql/queries/CreateMessage'
|
||||
@ -17,29 +14,28 @@ import { createRoomMutation } from '@graphql/queries/createRoomMutation'
|
||||
import { MarkMessagesAsSeen } from '@graphql/queries/MarkMessagesAsSeen'
|
||||
import { Message } from '@graphql/queries/Message'
|
||||
import { roomQuery } from '@graphql/queries/roomQuery'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser, pubsub })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
let chattingUser, otherChattingUser, notChattingUser
|
||||
|
||||
const database = databaseContext()
|
||||
const pubsub = pubsubContext()
|
||||
const pubsubSpy = jest.spyOn(pubsub, 'publish')
|
||||
|
||||
let server: ApolloServer
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database, pubsub })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -79,6 +75,10 @@ describe('Message', () => {
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
beforeAll(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
@ -128,7 +128,7 @@ describe('Message', () => {
|
||||
userId: 'other-chatting-user',
|
||||
},
|
||||
})
|
||||
roomId = room.data.CreateRoom.id
|
||||
roomId = (room.data as any).CreateRoom.id // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
})
|
||||
|
||||
describe('user chats in room', () => {
|
||||
@ -180,7 +180,7 @@ describe('Message', () => {
|
||||
lastMessageAt: expect.any(String),
|
||||
unreadCount: 0,
|
||||
lastMessage: expect.objectContaining({
|
||||
_id: result.data.Room[0].lastMessage.id,
|
||||
_id: result.data?.Room[0].lastMessage.id,
|
||||
id: expect.any(String),
|
||||
content: 'Some nice message to other chatting user',
|
||||
senderId: 'chatting-user',
|
||||
@ -410,7 +410,7 @@ describe('Message', () => {
|
||||
userId: 'other-chatting-user',
|
||||
},
|
||||
})
|
||||
roomId = room.data.CreateRoom.id
|
||||
roomId = (room.data as any).CreateRoom.id // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
await mutate({
|
||||
mutation: CreateMessage,
|
||||
@ -434,7 +434,7 @@ describe('Message', () => {
|
||||
Message: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
_id: result.data.Message[0].id,
|
||||
_id: result.data?.Message[0].id,
|
||||
indexId: 0,
|
||||
content: 'Some nice message to other chatting user',
|
||||
senderId: 'chatting-user',
|
||||
@ -642,7 +642,7 @@ describe('Message', () => {
|
||||
userId: 'other-chatting-user',
|
||||
},
|
||||
})
|
||||
roomId = room.data.CreateRoom.id
|
||||
roomId = (room.data as any).CreateRoom.id // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
await mutate({
|
||||
mutation: CreateMessage,
|
||||
variables: {
|
||||
@ -673,7 +673,7 @@ describe('Message', () => {
|
||||
roomId,
|
||||
},
|
||||
})
|
||||
msgs.data.Message.forEach((m) => messageIds.push(m.id))
|
||||
msgs.data?.Message.forEach((m) => messageIds.push(m.id))
|
||||
})
|
||||
|
||||
it('returns true', async () => {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import { withFilter } from 'graphql-subscriptions'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
import CONFIG, { isS3configured } from '@config/index'
|
||||
import CONFIG from '@config/index'
|
||||
import { CHAT_MESSAGE_ADDED } from '@constants/subscriptions'
|
||||
|
||||
import { attachments } from './attachments/attachments'
|
||||
@ -125,19 +125,17 @@ export default {
|
||||
|
||||
const atns: File[] = []
|
||||
|
||||
if (isS3configured(CONFIG)) {
|
||||
for await (const file of files) {
|
||||
const atn = await attachments(CONFIG).add(
|
||||
message,
|
||||
'ATTACHMENT',
|
||||
file,
|
||||
{},
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
)
|
||||
atns.push(atn)
|
||||
}
|
||||
for await (const file of files) {
|
||||
const atn = await attachments(CONFIG).add(
|
||||
message,
|
||||
'ATTACHMENT',
|
||||
file,
|
||||
{},
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
)
|
||||
atns.push(atn)
|
||||
}
|
||||
|
||||
return { ...message, files: atns }
|
||||
|
||||
@ -3,42 +3,40 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getDriver } from '@db/neo4j'
|
||||
import { markAllAsReadMutation } from '@graphql/queries/markAllAsReadMutation'
|
||||
import { markAsReadMutation } from '@graphql/queries/markAsReadMutation'
|
||||
import { notificationQuery } from '@graphql/queries/notificationQuery'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const driver = getDriver()
|
||||
let authenticatedUser
|
||||
let user
|
||||
let author
|
||||
let variables
|
||||
let query
|
||||
let mutate
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let query: ApolloTestSetup['query']
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
})
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
query = apolloSetup.query
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -157,7 +155,7 @@ describe('given some notifications', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await query({ query: notificationQuery() })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -241,7 +239,7 @@ describe('given some notifications', () => {
|
||||
variables: { ...variables, read: false },
|
||||
})
|
||||
await expect(response).toMatchObject(expected)
|
||||
await expect(response.data.notifications).toHaveLength(2) // double-check
|
||||
await expect(response.data?.notifications).toHaveLength(2) // double-check
|
||||
})
|
||||
|
||||
describe('if a resource gets deleted', () => {
|
||||
@ -288,7 +286,7 @@ describe('given some notifications', () => {
|
||||
mutation: markAsReadMutation(),
|
||||
variables: { ...variables, id: 'p1' },
|
||||
})
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(result.errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -307,7 +305,7 @@ describe('given some notifications', () => {
|
||||
|
||||
it('returns null', async () => {
|
||||
const response = await mutate({ mutation: markAsReadMutation(), variables })
|
||||
expect(response.data.markAsRead).toEqual(null)
|
||||
expect(response.data?.markAsRead).toEqual(null)
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@ -344,7 +342,7 @@ describe('given some notifications', () => {
|
||||
})
|
||||
it('returns null', async () => {
|
||||
const response = await mutate({ mutation: markAsReadMutation(), variables })
|
||||
expect(response.data.markAsRead).toEqual(null)
|
||||
expect(response.data?.markAsRead).toEqual(null)
|
||||
expect(response.errors).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@ -382,7 +380,7 @@ describe('given some notifications', () => {
|
||||
const result = await mutate({
|
||||
mutation: markAllAsReadMutation(),
|
||||
})
|
||||
expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(result.errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -400,7 +398,7 @@ describe('given some notifications', () => {
|
||||
|
||||
it('returns all as read', async () => {
|
||||
const response = await mutate({ mutation: markAllAsReadMutation(), variables })
|
||||
expect(response.data.markAllAsRead).toEqual(
|
||||
expect(response.data?.markAllAsRead).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
createdAt: '2019-08-30T19:33:48.651Z',
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { createPostMutation } from '@graphql/queries/createPostMutation'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let user
|
||||
let otherUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: true }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const createCommentMutation = gql`
|
||||
mutation ($id: ID, $postId: ID!, $content: String!) {
|
||||
@ -38,20 +38,13 @@ const postQuery = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -1,51 +1,45 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import registrationConstants from '@constants/registrationBranded'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
|
||||
import createPasswordReset from './helpers/createPasswordReset'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let variables
|
||||
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const getAllPasswordResets = async () => {
|
||||
const passwordResetQuery = await neode.cypher(
|
||||
const passwordResetQuery = await database.neode.cypher(
|
||||
'MATCH (passwordReset:PasswordReset) RETURN passwordReset',
|
||||
{},
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
const resets = passwordResetQuery.records.map((record) => record.get('passwordReset'))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return resets
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
})
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup()
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@ -129,7 +123,7 @@ describe('resetPassword', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const setup = async (options: any = {}) => {
|
||||
const { email = 'user@example.org', issuedAt = new Date(), nonce = '12345' } = options
|
||||
await createPasswordReset({ driver, email, issuedAt, nonce })
|
||||
await createPasswordReset({ driver: database.driver, email, issuedAt, nonce })
|
||||
}
|
||||
|
||||
const mutation = gql`
|
||||
|
||||
@ -8,13 +8,15 @@ import bcrypt from 'bcryptjs'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import registrationConstants from '@constants/registrationBranded'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import createPasswordReset from './helpers/createPasswordReset'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
requestPasswordReset: async (_parent, { email }, { driver }) => {
|
||||
requestPasswordReset: async (_parent, { email }, context: Context) => {
|
||||
const { driver } = context
|
||||
email = normalizeEmail(email)
|
||||
// TODO: why this is generated differntly from 'backend/src/schema/resolvers/helpers/generateNonce.js'?
|
||||
const nonce = uuid().substring(0, registrationConstants.NONCE_LENGTH)
|
||||
|
||||
@ -2,12 +2,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import Image from '@db/models/Image'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
@ -15,30 +11,32 @@ import { createPostMutation } from '@graphql/queries/createPostMutation'
|
||||
import { Post } from '@graphql/queries/Post'
|
||||
import { pushPost } from '@graphql/queries/pushPost'
|
||||
import { unpushPost } from '@graphql/queries/unpushPost'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let user
|
||||
|
||||
const database = databaseContext()
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let server: ApolloServer
|
||||
let authenticatedUser
|
||||
let query, mutate
|
||||
const defaultConfig = {
|
||||
CATEGORIES_ACTIVE: true,
|
||||
// MAPBOX_TOKEN: CONFIG.MAPBOX_TOKEN,
|
||||
}
|
||||
let config: Partial<Context['config']>
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
mutate = createTestClientResult.mutate
|
||||
query = createTestClientResult.query
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -51,6 +49,7 @@ const categoryIds = ['cat9', 'cat4', 'cat15']
|
||||
let variables
|
||||
|
||||
beforeEach(async () => {
|
||||
config = { ...defaultConfig }
|
||||
variables = {}
|
||||
user = await Factory.build(
|
||||
'user',
|
||||
@ -271,7 +270,7 @@ describe('CreatePost', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: createPostMutation(), variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -708,7 +707,7 @@ describe('UpdatePost', () => {
|
||||
categoryIds,
|
||||
},
|
||||
})
|
||||
newlyCreatedPost = data.CreatePost
|
||||
newlyCreatedPost = (data as any).CreatePost // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
variables = {
|
||||
id: newlyCreatedPost.id,
|
||||
title: 'New title',
|
||||
@ -733,7 +732,7 @@ describe('UpdatePost', () => {
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: updatePostMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -771,7 +770,7 @@ describe('UpdatePost', () => {
|
||||
it('updates the updatedAt attribute', async () => {
|
||||
const {
|
||||
data: { UpdatePost },
|
||||
} = await mutate({ mutation: updatePostMutation, variables })
|
||||
} = (await mutate({ mutation: updatePostMutation, variables })) as any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
expect(UpdatePost.updatedAt).toBeTruthy()
|
||||
expect(Date.parse(UpdatePost.updatedAt)).toEqual(expect.any(Number))
|
||||
expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePost.updatedAt)
|
||||
@ -1377,7 +1376,8 @@ describe('pin posts', () => {
|
||||
|
||||
describe('MAX_PINNED_POSTS is 0', () => {
|
||||
beforeEach(async () => {
|
||||
CONFIG.MAX_PINNED_POSTS = 0
|
||||
config = { ...defaultConfig, MAX_PINNED_POSTS: 0 }
|
||||
|
||||
await Factory.build(
|
||||
'post',
|
||||
{
|
||||
@ -1400,7 +1400,7 @@ describe('pin posts', () => {
|
||||
|
||||
describe('MAX_PINNED_POSTS is 1', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.MAX_PINNED_POSTS = 1
|
||||
config = { ...defaultConfig, MAX_PINNED_POSTS: 1 }
|
||||
})
|
||||
|
||||
describe('are allowed to pin posts', () => {
|
||||
@ -1752,7 +1752,8 @@ describe('pin posts', () => {
|
||||
const postsPinnedCountsQuery = `query { PostsPinnedCounts { maxPinnedPosts, currentlyPinnedPosts } }`
|
||||
|
||||
beforeEach(async () => {
|
||||
CONFIG.MAX_PINNED_POSTS = 3
|
||||
config = { ...defaultConfig, MAX_PINNED_POSTS: 3 }
|
||||
|
||||
await Factory.build(
|
||||
'post',
|
||||
{
|
||||
@ -2127,7 +2128,7 @@ describe('DeletePost', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: deletePostMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2138,7 +2139,7 @@ describe('DeletePost', () => {
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
const { errors } = await mutate({ mutation: deletePostMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2280,7 +2281,7 @@ describe('emotions', () => {
|
||||
variables,
|
||||
})
|
||||
|
||||
expect(addPostEmotions.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(addPostEmotions.errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -2401,7 +2402,7 @@ describe('emotions', () => {
|
||||
mutation: removePostEmotionsMutation,
|
||||
variables: removePostEmotionsVariables,
|
||||
})
|
||||
expect(removePostEmotions.errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(removePostEmotions.errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -9,8 +9,7 @@ import { isEmpty } from 'lodash'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
import { validateEventParams } from './helpers/events'
|
||||
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
|
||||
@ -41,7 +40,7 @@ const filterEventDates = (params) => {
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Post: async (object, params, context, resolveInfo) => {
|
||||
Post: async (object, params, context: Context, resolveInfo) => {
|
||||
params = await filterPostsOfMyGroups(params, context)
|
||||
params = await filterInvisiblePosts(params, context)
|
||||
params = await filterForMutedUsers(params, context)
|
||||
@ -77,10 +76,13 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
PostsEmotionsByCurrentUser: async (_object, params, context, _resolveInfo) => {
|
||||
PostsEmotionsByCurrentUser: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { postId } = params
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (transaction) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const emotionsTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $userId})-[emoted:EMOTED]->(post:Post {id: $postId})
|
||||
@ -94,23 +96,29 @@ export default {
|
||||
const [emotions] = await readTxResultPromise
|
||||
return emotions
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
PostsPinnedCounts: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { config } = context
|
||||
const [postsPinnedCount] = (
|
||||
await context.database.query({
|
||||
query: 'MATCH (p:Post { pinned: true }) RETURN COUNT (p) AS count',
|
||||
})
|
||||
).records.map((r) => Number(r.get('count').toString()))
|
||||
return {
|
||||
maxPinnedPosts: CONFIG.MAX_PINNED_POSTS,
|
||||
maxPinnedPosts: config.MAX_PINNED_POSTS,
|
||||
currentlyPinnedPosts: postsPinnedCount,
|
||||
}
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
CreatePost: async (_parent, params, context, _resolveInfo) => {
|
||||
CreatePost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { user } = context
|
||||
if (!user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const { config } = context
|
||||
const { categoryIds, groupId } = params
|
||||
const { image: imageInput } = params
|
||||
|
||||
@ -146,7 +154,7 @@ export default {
|
||||
)`
|
||||
}
|
||||
const categoriesCypher =
|
||||
CONFIG.CATEGORIES_ACTIVE && categoryIds
|
||||
config.CATEGORIES_ACTIVE && categoryIds
|
||||
? `WITH post
|
||||
UNWIND $categoryIds AS categoryId
|
||||
MATCH (category:Category {id: categoryId})
|
||||
@ -173,18 +181,18 @@ export default {
|
||||
${groupCypher}
|
||||
RETURN post {.*, postType: [l IN labels(post) WHERE NOT l = 'Post'] }
|
||||
`,
|
||||
{ userId: context.user.id, categoryIds, groupId, params },
|
||||
{ userId: user.id, categoryIds, groupId, params },
|
||||
)
|
||||
const [post] = createPostTransactionResponse.records.map((record) => record.get('post'))
|
||||
if (imageInput) {
|
||||
await images.mergeImage(post, 'HERO_IMAGE', imageInput, { transaction })
|
||||
await images(context.config).mergeImage(post, 'HERO_IMAGE', imageInput, { transaction })
|
||||
}
|
||||
return post
|
||||
})
|
||||
try {
|
||||
const post = await writeTxResultPromise
|
||||
if (locationName) {
|
||||
await createOrUpdateLocations('Post', post.id, locationName, session)
|
||||
await createOrUpdateLocations('Post', post.id, locationName, session, context)
|
||||
}
|
||||
return post
|
||||
} catch (e) {
|
||||
@ -192,10 +200,11 @@ export default {
|
||||
throw new UserInputError('Post with this slug already exists!')
|
||||
throw new Error(e)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
UpdatePost: async (_parent, params, context, _resolveInfo) => {
|
||||
UpdatePost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { config } = context
|
||||
const { categoryIds } = params
|
||||
const { image: imageInput } = params
|
||||
|
||||
@ -211,7 +220,7 @@ export default {
|
||||
WITH post
|
||||
`
|
||||
|
||||
if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
if (config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) {
|
||||
const cypherDeletePreviousRelations = `
|
||||
MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category)
|
||||
DELETE previousRelations
|
||||
@ -248,20 +257,20 @@ export default {
|
||||
updatePostVariables,
|
||||
)
|
||||
const [post] = updatePostTransactionResponse.records.map((record) => record.get('post'))
|
||||
await images.mergeImage(post, 'HERO_IMAGE', imageInput, { transaction })
|
||||
await images(context.config).mergeImage(post, 'HERO_IMAGE', imageInput, { transaction })
|
||||
return post
|
||||
})
|
||||
const post = await writeTxResultPromise
|
||||
if (locationName) {
|
||||
await createOrUpdateLocations('Post', post.id, locationName, session)
|
||||
await createOrUpdateLocations('Post', post.id, locationName, session, context)
|
||||
}
|
||||
return post
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
|
||||
DeletePost: async (_object, args, context, _resolveInfo) => {
|
||||
DeletePost: async (_object, args, context: Context, _resolveInfo) => {
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const deletePostTransactionResponse = await transaction.run(
|
||||
@ -278,17 +287,17 @@ export default {
|
||||
{ postId: args.id },
|
||||
)
|
||||
const [post] = deletePostTransactionResponse.records.map((record) => record.get('post'))
|
||||
await images.deleteImage(post, 'HERO_IMAGE', { transaction })
|
||||
await images(context.config).deleteImage(post, 'HERO_IMAGE', { transaction })
|
||||
return post
|
||||
})
|
||||
try {
|
||||
const post = await writeTxResultPromise
|
||||
return post
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
AddPostEmotions: async (_object, params, context, _resolveInfo) => {
|
||||
AddPostEmotions: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { to, data } = params
|
||||
const { user } = context
|
||||
const session = context.driver.session()
|
||||
@ -312,7 +321,7 @@ export default {
|
||||
const [emoted] = await writeTxResultPromise
|
||||
return emoted
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
RemovePostEmotions: async (_object, params, context, _resolveInfo) => {
|
||||
@ -344,7 +353,11 @@ export default {
|
||||
}
|
||||
},
|
||||
pinPost: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
if (CONFIG.MAX_PINNED_POSTS === 0) throw new Error('Pinned posts are not allowed!')
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const { config } = context
|
||||
if (config.MAX_PINNED_POSTS === 0) throw new Error('Pinned posts are not allowed!')
|
||||
let pinnedPostWithNestedAttributes
|
||||
const { driver, user } = context
|
||||
const session = driver.session()
|
||||
@ -358,7 +371,7 @@ export default {
|
||||
SET post.pinned = true
|
||||
RETURN post, pinned.createdAt as pinnedAt`
|
||||
|
||||
if (CONFIG.MAX_PINNED_POSTS === 1) {
|
||||
if (config.MAX_PINNED_POSTS === 1) {
|
||||
let writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const deletePreviousRelationsResponse = await transaction.run(
|
||||
`
|
||||
@ -403,7 +416,7 @@ export default {
|
||||
query: `MATCH (:User)-[:PINNED]->(post:Post { pinned: true }) RETURN COUNT(post) AS count`,
|
||||
})
|
||||
).records.map((r) => Number(r.get('count').toString()))
|
||||
if (currentPinnedPostCount >= CONFIG.MAX_PINNED_POSTS) {
|
||||
if (currentPinnedPostCount >= config.MAX_PINNED_POSTS) {
|
||||
throw new Error('Max number of pinned posts is reached!')
|
||||
}
|
||||
const [pinPostResult] = (
|
||||
|
||||
@ -2,11 +2,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
|
||||
import { createCommentMutation } from '@graphql/queries/createCommentMutation'
|
||||
@ -18,9 +13,9 @@ import { postQuery } from '@graphql/queries/postQuery'
|
||||
import { profilePagePosts } from '@graphql/queries/profilePagePosts'
|
||||
import { searchPosts } from '@graphql/queries/searchPosts'
|
||||
import { signupVerificationMutation } from '@graphql/queries/signupVerificationMutation'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
jest.mock('@constants/groups', () => {
|
||||
return {
|
||||
@ -29,30 +24,28 @@ jest.mock('@constants/groups', () => {
|
||||
}
|
||||
})
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let anyUser
|
||||
let allGroupsUser
|
||||
let pendingUser
|
||||
let publicUser
|
||||
let closedUser
|
||||
let hiddenUser
|
||||
let authenticatedUser
|
||||
let newUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -545,7 +538,7 @@ describe('Posts in Groups', () => {
|
||||
describe('visibility of posts', () => {
|
||||
describe('query post by ID', () => {
|
||||
describe('without authentication', () => {
|
||||
beforeAll(async () => {
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
@ -608,7 +601,7 @@ describe('Posts in Groups', () => {
|
||||
termsAndConditionsAgreedVersion: '0.0.1',
|
||||
},
|
||||
})
|
||||
newUser = result.data.SignupVerification
|
||||
newUser = result.data?.SignupVerification
|
||||
authenticatedUser = newUser
|
||||
})
|
||||
|
||||
@ -802,13 +795,13 @@ describe('Posts in Groups', () => {
|
||||
|
||||
describe('filter posts', () => {
|
||||
describe('without authentication', () => {
|
||||
beforeAll(async () => {
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data?.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -838,7 +831,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data?.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -868,7 +861,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data?.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -898,7 +891,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data?.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -928,7 +921,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows all posts', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -966,13 +959,13 @@ describe('Posts in Groups', () => {
|
||||
|
||||
describe('profile page posts', () => {
|
||||
describe('without authentication', () => {
|
||||
beforeAll(async () => {
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: profilePagePosts(), variables: {} })
|
||||
expect(result.data.profilePagePosts).toHaveLength(2)
|
||||
expect(result.data?.profilePagePosts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
profilePagePosts: expect.arrayContaining([
|
||||
@ -1000,7 +993,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: profilePagePosts(), variables: {} })
|
||||
expect(result.data.profilePagePosts).toHaveLength(2)
|
||||
expect(result.data?.profilePagePosts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
profilePagePosts: expect.arrayContaining([
|
||||
@ -1028,7 +1021,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: profilePagePosts(), variables: {} })
|
||||
expect(result.data.profilePagePosts).toHaveLength(2)
|
||||
expect(result.data?.profilePagePosts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
profilePagePosts: expect.arrayContaining([
|
||||
@ -1056,7 +1049,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the post of the public group and the post without group', async () => {
|
||||
const result = await query({ query: profilePagePosts(), variables: {} })
|
||||
expect(result.data.profilePagePosts).toHaveLength(2)
|
||||
expect(result.data?.profilePagePosts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
profilePagePosts: expect.arrayContaining([
|
||||
@ -1084,7 +1077,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows all posts', async () => {
|
||||
const result = await query({ query: profilePagePosts(), variables: {} })
|
||||
expect(result.data.profilePagePosts).toHaveLength(4)
|
||||
expect(result.data?.profilePagePosts).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
profilePagePosts: expect.arrayContaining([
|
||||
@ -1118,7 +1111,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
describe('searchPosts', () => {
|
||||
describe('without authentication', () => {
|
||||
beforeAll(async () => {
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
@ -1131,7 +1124,7 @@ describe('Posts in Groups', () => {
|
||||
firstPosts: 25,
|
||||
},
|
||||
})
|
||||
expect(result.data.searchPosts.posts).toHaveLength(0)
|
||||
expect(result.data?.searchPosts.posts).toHaveLength(0)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
searchPosts: {
|
||||
@ -1157,7 +1150,7 @@ describe('Posts in Groups', () => {
|
||||
firstPosts: 25,
|
||||
},
|
||||
})
|
||||
expect(result.data.searchPosts.posts).toHaveLength(2)
|
||||
expect(result.data?.searchPosts.posts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
searchPosts: {
|
||||
@ -1194,7 +1187,7 @@ describe('Posts in Groups', () => {
|
||||
firstPosts: 25,
|
||||
},
|
||||
})
|
||||
expect(result.data.searchPosts.posts).toHaveLength(2)
|
||||
expect(result.data?.searchPosts.posts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
searchPosts: {
|
||||
@ -1231,7 +1224,7 @@ describe('Posts in Groups', () => {
|
||||
firstPosts: 25,
|
||||
},
|
||||
})
|
||||
expect(result.data.searchPosts.posts).toHaveLength(2)
|
||||
expect(result.data?.searchPosts.posts).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
searchPosts: {
|
||||
@ -1268,7 +1261,7 @@ describe('Posts in Groups', () => {
|
||||
firstPosts: 25,
|
||||
},
|
||||
})
|
||||
expect(result.data.searchPosts.posts).toHaveLength(4)
|
||||
expect(result.data?.searchPosts.posts).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
searchPosts: {
|
||||
@ -1321,7 +1314,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the posts of the closed group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(3)
|
||||
expect(result.data?.Post).toHaveLength(3)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1366,7 +1359,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows all the posts', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1419,7 +1412,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('does not show the posts of the closed group anymore', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(3)
|
||||
expect(result.data?.Post).toHaveLength(3)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1464,7 +1457,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows only the public posts', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data?.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1503,7 +1496,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('still shows the posts of the public group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1552,7 +1545,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('stil shows the posts of the closed group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1601,7 +1594,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('still shows the post of the hidden group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1654,7 +1647,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows the posts of the closed group', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1705,7 +1698,7 @@ describe('Posts in Groups', () => {
|
||||
|
||||
it('shows all posts', async () => {
|
||||
const result = await query({ query: filterPosts(), variables: {} })
|
||||
expect(result.data.Post).toHaveLength(4)
|
||||
expect(result.data?.Post).toHaveLength(4)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
@ -1752,7 +1745,7 @@ describe('Posts in Groups', () => {
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postsInMyGroups: true } },
|
||||
})
|
||||
expect(result.data.Post).toHaveLength(0)
|
||||
expect(result.data?.Post).toHaveLength(0)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: [],
|
||||
@ -1773,7 +1766,7 @@ describe('Posts in Groups', () => {
|
||||
query: filterPosts(),
|
||||
variables: { filter: { postsInMyGroups: true } },
|
||||
})
|
||||
expect(result.data.Post).toHaveLength(2)
|
||||
expect(result.data?.Post).toHaveLength(2)
|
||||
expect(result).toMatchObject({
|
||||
data: {
|
||||
Post: expect.arrayContaining([
|
||||
|
||||
@ -1,36 +1,30 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import EmailAddress from '@db/models/EmailAddress'
|
||||
import User from '@db/models/User'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let variables
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
let authenticatedUser
|
||||
let mutate
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
let config: Partial<Context['config']> = {}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -40,6 +34,7 @@ afterAll(() => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
config = {}
|
||||
variables = {}
|
||||
})
|
||||
|
||||
@ -62,11 +57,13 @@ describe('Signup', () => {
|
||||
describe('unauthenticated', () => {
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
config = {
|
||||
INVITE_REGISTRATION: false,
|
||||
PUBLIC_REGISTRATION: false,
|
||||
}
|
||||
})
|
||||
|
||||
it('throws AuthorizationError', async () => {
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
errors: [{ message: 'Not Authorized!' }],
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@ import { UserInputError } from 'apollo-server'
|
||||
import { hash } from 'bcryptjs'
|
||||
|
||||
import { getNeode } from '@db/neo4j'
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
import existingEmailAddress from './helpers/existingEmailAddress'
|
||||
import generateNonce from './helpers/generateNonce'
|
||||
@ -106,7 +106,7 @@ export default {
|
||||
await redeemInviteCode(context, inviteCode, true)
|
||||
}
|
||||
|
||||
await createOrUpdateLocations('User', user.id, locationName, session)
|
||||
await createOrUpdateLocations('User', user.id, locationName, session, context)
|
||||
return user
|
||||
} catch (e) {
|
||||
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
|
||||
|
||||
@ -1,46 +1,38 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import { CreateMessage } from '@graphql/queries/CreateMessage'
|
||||
import { createRoomMutation } from '@graphql/queries/createRoomMutation'
|
||||
import { roomQuery } from '@graphql/queries/roomQuery'
|
||||
import { unreadRoomsQuery } from '@graphql/queries/unreadRoomsQuery'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let chattingUser, otherChattingUser, notChattingUser
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
describe('Room', () => {
|
||||
@ -73,6 +65,10 @@ describe('Room', () => {
|
||||
|
||||
describe('create room', () => {
|
||||
describe('unauthenticated', () => {
|
||||
beforeAll(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
@ -133,13 +129,13 @@ describe('Room', () => {
|
||||
userId: 'other-chatting-user',
|
||||
},
|
||||
})
|
||||
roomId = result.data.CreateRoom.id
|
||||
roomId = (result.data as any).CreateRoom.id
|
||||
expect(result).toMatchObject({
|
||||
errors: undefined,
|
||||
data: {
|
||||
CreateRoom: {
|
||||
id: expect.any(String),
|
||||
roomId: result.data.CreateRoom.id,
|
||||
roomId: (result.data as any).CreateRoom.id,
|
||||
roomName: 'Other Chatting User',
|
||||
unreadCount: 0,
|
||||
users: expect.arrayContaining([
|
||||
@ -215,7 +211,7 @@ describe('Room', () => {
|
||||
Room: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
roomId: result.data.Room[0].id,
|
||||
roomId: (result.data as any).Room[0].id,
|
||||
roomName: 'Other Chatting User',
|
||||
users: expect.arrayContaining([
|
||||
{
|
||||
@ -255,7 +251,7 @@ describe('Room', () => {
|
||||
Room: [
|
||||
{
|
||||
id: expect.any(String),
|
||||
roomId: result.data.Room[0].id,
|
||||
roomId: (result.data as any).Room[0].id,
|
||||
roomName: 'Chatting User',
|
||||
unreadCount: 0,
|
||||
users: expect.arrayContaining([
|
||||
@ -325,7 +321,7 @@ describe('Room', () => {
|
||||
userId: 'not-chatting-user',
|
||||
},
|
||||
})
|
||||
otherRoomId = result.data.CreateRoom.roomId
|
||||
otherRoomId = (result.data as any).CreateRoom.roomId
|
||||
await mutate({
|
||||
mutation: CreateMessage,
|
||||
variables: {
|
||||
@ -354,7 +350,7 @@ describe('Room', () => {
|
||||
userId: 'not-chatting-user',
|
||||
},
|
||||
})
|
||||
otherRoomId = result2.data.CreateRoom.roomId
|
||||
otherRoomId = (result2.data as any).CreateRoom.roomId
|
||||
await mutate({
|
||||
mutation: CreateMessage,
|
||||
variables: {
|
||||
@ -591,7 +587,6 @@ describe('Room', () => {
|
||||
})
|
||||
|
||||
describe('query single room', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let result: any = null
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
@ -2,30 +2,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { statistics } from '@graphql/queries/statistics'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
let query, authenticatedUser
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
let query: ApolloTestSetup['query']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
const apolloSetup = createApolloTestSetup()
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/dot-notation */
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
|
||||
@ -1,31 +1,32 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable jest/unbound-method */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { verify } from 'jsonwebtoken'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { categories } from '@constants/categories'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import { loginMutation } from '@graphql/queries/loginMutation'
|
||||
import encode from '@jwt/encode'
|
||||
import createServer, { context } from '@src/server'
|
||||
import { decode } from '@jwt/decode'
|
||||
import { encode } from '@jwt/encode'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup, TEST_CONFIG } from '@root/test/helpers'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
|
||||
let query, mutate, variables, req, user
|
||||
const jwt = { verify }
|
||||
let variables, req, user
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const disable = async (id) => {
|
||||
const moderator = await Factory.build('user', { id: 'u2', role: 'moderator' })
|
||||
const user = await neode.find('User', id)
|
||||
const user = await database.neode.find('User', id)
|
||||
const reportAgainstUser = await Factory.build('report')
|
||||
await Promise.all([
|
||||
reportAgainstUser.relateTo(moderator, 'filed', {
|
||||
@ -42,23 +43,34 @@ const disable = async (id) => {
|
||||
])
|
||||
}
|
||||
|
||||
const config = {
|
||||
JWT_SECRET: 'I am the JWT secret',
|
||||
JWT_EXPIRES: TEST_CONFIG.JWT_EXPIRES,
|
||||
CLIENT_URI: TEST_CONFIG.CLIENT_URI,
|
||||
GRAPHQL_URI: TEST_CONFIG.GRAPHQL_URI,
|
||||
}
|
||||
const context = { config }
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
// One of the rare occasions where we test
|
||||
// the actual `context` implementation here
|
||||
return context({ req })
|
||||
},
|
||||
})
|
||||
query = createTestClient(server).query
|
||||
mutate = createTestClient(server).mutate
|
||||
const context = async () => {
|
||||
const authenticatedUser = await decode({ driver: database.driver, config })(
|
||||
req.headers.authorization,
|
||||
)
|
||||
return { authenticatedUser, config }
|
||||
}
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@ -120,7 +132,7 @@ describe('currentUser', () => {
|
||||
avatar,
|
||||
},
|
||||
)
|
||||
const userBearerToken = encode({ id: 'u3' })
|
||||
const userBearerToken = encode(context)({ id: 'u3' })
|
||||
req = { headers: { authorization: `Bearer ${userBearerToken}` } }
|
||||
})
|
||||
|
||||
@ -203,11 +215,11 @@ describe('currentUser', () => {
|
||||
|
||||
it('returns only the saved active categories', async () => {
|
||||
const result = await query({ query: currentUserQuery, variables })
|
||||
expect(result.data.currentUser.activeCategories).toHaveLength(4)
|
||||
expect(result.data.currentUser.activeCategories).toContain('cat1')
|
||||
expect(result.data.currentUser.activeCategories).toContain('cat3')
|
||||
expect(result.data.currentUser.activeCategories).toContain('cat5')
|
||||
expect(result.data.currentUser.activeCategories).toContain('cat7')
|
||||
expect(result.data?.currentUser.activeCategories).toHaveLength(4)
|
||||
expect(result.data?.currentUser.activeCategories).toContain('cat1')
|
||||
expect(result.data?.currentUser.activeCategories).toContain('cat3')
|
||||
expect(result.data?.currentUser.activeCategories).toContain('cat5')
|
||||
expect(result.data?.currentUser.activeCategories).toContain('cat7')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -236,8 +248,8 @@ describe('login', () => {
|
||||
it('responds with a JWT bearer token', async () => {
|
||||
const {
|
||||
data: { login: token },
|
||||
} = await mutate({ mutation: loginMutation, variables })
|
||||
jwt.verify(token, CONFIG.JWT_SECRET, (err, data) => {
|
||||
} = (await mutate({ mutation: loginMutation, variables })) as any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
jwt.verify(token, config.JWT_SECRET, (err, data) => {
|
||||
expect(data).toMatchObject({
|
||||
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
|
||||
})
|
||||
@ -274,7 +286,7 @@ describe('login', () => {
|
||||
describe('normalization', () => {
|
||||
describe('email address is a gmail address ', () => {
|
||||
beforeEach(async () => {
|
||||
const email = await neode.first(
|
||||
const email = await database.neode.first(
|
||||
'EmailAddress',
|
||||
{ email: 'test@example.org' },
|
||||
undefined,
|
||||
@ -354,7 +366,7 @@ describe('change password', () => {
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
await Factory.build('user', { id: 'u3' })
|
||||
const userBearerToken = encode({ id: 'u3' })
|
||||
const userBearerToken = encode(context)({ id: 'u3' })
|
||||
req = { headers: { authorization: `Bearer ${userBearerToken}` } }
|
||||
})
|
||||
describe('old password === new password', () => {
|
||||
|
||||
@ -9,7 +9,8 @@ import bcrypt from 'bcryptjs'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
import { getNeode } from '@db/neo4j'
|
||||
import encode from '@jwt/encode'
|
||||
import { encode } from '@jwt/encode'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
|
||||
@ -21,7 +22,8 @@ export default {
|
||||
neo4jgraphql(object, { id: context.user.id }, context, resolveInfo),
|
||||
},
|
||||
Mutation: {
|
||||
login: async (_, { email, password }, { driver }) => {
|
||||
login: async (_, { email, password }, context: Context) => {
|
||||
const { driver } = context
|
||||
// if (user && user.id) {
|
||||
// throw new Error('Already logged in.')
|
||||
// }
|
||||
@ -45,17 +47,21 @@ export default {
|
||||
!currentUser.disabled
|
||||
) {
|
||||
delete currentUser.encryptedPassword
|
||||
return encode(currentUser)
|
||||
return encode(context)(currentUser)
|
||||
} else if (currentUser?.disabled) {
|
||||
throw new AuthenticationError('Your account has been disabled.')
|
||||
} else {
|
||||
throw new AuthenticationError('Incorrect email address or password.')
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
changePassword: async (_, { oldPassword, newPassword }, { user }) => {
|
||||
changePassword: async (_, { oldPassword, newPassword }, context: Context) => {
|
||||
if (!context.user) {
|
||||
throw new Error('Missing authenticated user.')
|
||||
}
|
||||
const { user } = context
|
||||
const currentUser = await neode.find('User', user.id)
|
||||
|
||||
const encryptedPassword = currentUser.get<string>('encryptedPassword')
|
||||
@ -73,7 +79,7 @@ export default {
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
|
||||
return encode(await currentUser.toJson())
|
||||
return encode(context)(await currentUser.toJson())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -3,29 +3,34 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { categories } from '@constants/categories'
|
||||
import databaseContext from '@context/database'
|
||||
import pubsubContext from '@context/pubsub'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import User from '@db/models/User'
|
||||
import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
import type { DecodedUser } from '@src/jwt/decode'
|
||||
// import CONFIG from '@src/config'
|
||||
|
||||
const categoryIds = ['cat9']
|
||||
let user
|
||||
let admin
|
||||
let authenticatedUser
|
||||
|
||||
let query
|
||||
let mutate
|
||||
let variables
|
||||
|
||||
const pubsub = pubsubContext()
|
||||
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser, pubsub })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const deleteUserMutation = gql`
|
||||
mutation ($id: ID!, $resource: [Deletable]) {
|
||||
DeleteUser(id: $id, resource: $resource) {
|
||||
@ -94,21 +99,13 @@ const resetTrophyBadgesSelected = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database, pubsub })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -118,6 +115,10 @@ afterAll(async () => {
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
afterEach(async () => {
|
||||
await cleanDatabase()
|
||||
@ -128,6 +129,11 @@ describe('User', () => {
|
||||
let userQuery
|
||||
|
||||
beforeEach(async () => {
|
||||
const user = await Factory.build('user', {
|
||||
id: 'user',
|
||||
role: 'user',
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
userQuery = gql`
|
||||
query ($email: String) {
|
||||
User(email: $email) {
|
||||
@ -254,7 +260,7 @@ describe('UpdateUser', () => {
|
||||
|
||||
it('is not allowed to change other user accounts', async () => {
|
||||
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
|
||||
})
|
||||
})
|
||||
|
||||
@ -326,7 +332,7 @@ describe('UpdateUser', () => {
|
||||
termsAndConditionsAgreedVersion: 'invalid version format',
|
||||
}
|
||||
const { errors } = await mutate({ mutation: updateUserMutation, variables })
|
||||
expect(errors[0]).toHaveProperty('message', 'Invalid version format!')
|
||||
expect(errors?.[0]).toHaveProperty('message', 'Invalid version format!')
|
||||
})
|
||||
|
||||
describe('supports updating location', () => {
|
||||
@ -684,7 +690,10 @@ describe('emailNotificationSettings', () => {
|
||||
it('returns the emailNotificationSettings', async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
await expect(
|
||||
query({ query: emailNotificationSettingsQuery, variables: { id: authenticatedUser.id } }),
|
||||
query({
|
||||
query: emailNotificationSettingsQuery,
|
||||
variables: { id: authenticatedUser?.id },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
@ -778,7 +787,7 @@ describe('emailNotificationSettings', () => {
|
||||
|
||||
describe('as self', () => {
|
||||
it('updates the emailNotificationSettings', async () => {
|
||||
authenticatedUser = await user.toJson()
|
||||
authenticatedUser = (await user.toJson()) as DecodedUser
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: emailNotificationSettingsMutation,
|
||||
@ -876,7 +885,7 @@ describe('save category settings', () => {
|
||||
|
||||
describe('not authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = undefined
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
@ -921,7 +930,7 @@ describe('save category settings', () => {
|
||||
|
||||
it('returns the active categories when user is queried', async () => {
|
||||
await expect(
|
||||
query({ query: userQuery, variables: { id: authenticatedUser.id } }),
|
||||
query({ query: userQuery, variables: { id: authenticatedUser?.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
@ -963,7 +972,7 @@ describe('save category settings', () => {
|
||||
|
||||
it('returns the new active categories when user is queried', async () => {
|
||||
await expect(
|
||||
query({ query: userQuery, variables: { id: authenticatedUser.id } }),
|
||||
query({ query: userQuery, variables: { id: authenticatedUser?.id } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
@ -1000,7 +1009,7 @@ describe('updateOnlineStatus', () => {
|
||||
|
||||
describe('not authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = undefined
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
@ -1030,7 +1039,7 @@ describe('updateOnlineStatus', () => {
|
||||
)
|
||||
|
||||
const cypher = 'MATCH (u:User {id: $id}) RETURN u'
|
||||
const result = await database.neode.cypher(cypher, { id: authenticatedUser.id })
|
||||
const result = await database.neode.cypher(cypher, { id: authenticatedUser?.id })
|
||||
const dbUser = database.neode.hydrateFirst(result, 'u', database.neode.model('User'))
|
||||
await expect(dbUser.toJson()).resolves.toMatchObject({
|
||||
lastOnlineStatus: 'online',
|
||||
@ -1056,7 +1065,7 @@ describe('updateOnlineStatus', () => {
|
||||
)
|
||||
|
||||
const cypher = 'MATCH (u:User {id: $id}) RETURN u'
|
||||
const result = await database.neode.cypher(cypher, { id: authenticatedUser.id })
|
||||
const result = await database.neode.cypher(cypher, { id: authenticatedUser?.id })
|
||||
const dbUser = database.neode.hydrateFirst(result, 'u', database.neode.model('User'))
|
||||
await expect(dbUser.toJson()).resolves.toMatchObject({
|
||||
lastOnlineStatus: 'away',
|
||||
@ -1072,7 +1081,7 @@ describe('updateOnlineStatus', () => {
|
||||
)
|
||||
|
||||
const cypher = 'MATCH (u:User {id: $id}) RETURN u'
|
||||
const result = await database.neode.cypher(cypher, { id: authenticatedUser.id })
|
||||
const result = await database.neode.cypher(cypher, { id: authenticatedUser?.id })
|
||||
const dbUser = database.neode.hydrateFirst<typeof User>(
|
||||
result,
|
||||
'u',
|
||||
@ -1091,7 +1100,7 @@ describe('updateOnlineStatus', () => {
|
||||
}),
|
||||
)
|
||||
|
||||
const result2 = await database.neode.cypher(cypher, { id: authenticatedUser.id })
|
||||
const result2 = await database.neode.cypher(cypher, { id: authenticatedUser?.id })
|
||||
const dbUser2 = database.neode.hydrateFirst(result2, 'u', database.neode.model('User'))
|
||||
await expect(dbUser2.toJson()).resolves.toMatchObject({
|
||||
lastOnlineStatus: 'away',
|
||||
@ -1133,7 +1142,7 @@ describe('setTrophyBadgeSelected', () => {
|
||||
|
||||
describe('not authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = undefined
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
@ -1515,8 +1524,8 @@ describe('resetTrophyBadgesSelected', () => {
|
||||
})
|
||||
|
||||
describe('not authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = undefined
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
|
||||
@ -10,7 +10,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
|
||||
import { getNeode } from '@db/neo4j'
|
||||
import { Context } from '@src/server'
|
||||
import { Context } from '@src/context'
|
||||
|
||||
import { defaultTrophyBadge, defaultVerificationBadge } from './badges'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
@ -168,10 +168,10 @@ export default {
|
||||
} catch (error) {
|
||||
throw new UserInputError(error.message)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
UpdateUser: async (_parent, params, context, _resolveInfo) => {
|
||||
UpdateUser: async (_parent, params, context: Context, _resolveInfo) => {
|
||||
const { avatar: avatarInput } = params
|
||||
delete params.avatar
|
||||
params.locationName = params.locationName === '' ? null : params.locationName
|
||||
@ -210,22 +210,24 @@ export default {
|
||||
)
|
||||
const [user] = updateUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
if (avatarInput) {
|
||||
await images.mergeImage(user, 'AVATAR_IMAGE', avatarInput, { transaction })
|
||||
await images(context.config).mergeImage(user, 'AVATAR_IMAGE', avatarInput, {
|
||||
transaction,
|
||||
})
|
||||
}
|
||||
return user
|
||||
})
|
||||
try {
|
||||
const user = await writeTxResultPromise
|
||||
// TODO: put in a middleware, see "CreateGroup", "UpdateGroup"
|
||||
await createOrUpdateLocations('User', params.id, params.locationName, session)
|
||||
await createOrUpdateLocations('User', params.id, params.locationName, session, context)
|
||||
return user
|
||||
} catch (error) {
|
||||
throw new UserInputError(error.message)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
DeleteUser: async (_object, params, context, _resolveInfo) => {
|
||||
DeleteUser: async (_object, params, context: Context, _resolveInfo) => {
|
||||
const { resource, id: userId } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
@ -253,7 +255,9 @@ export default {
|
||||
return Promise.all(
|
||||
txResult.records
|
||||
.map((record) => record.get('resource'))
|
||||
.map((resource) => images.deleteImage(resource, 'HERO_IMAGE', { transaction })),
|
||||
.map((resource) =>
|
||||
images(context.config).deleteImage(resource, 'HERO_IMAGE', { transaction }),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
@ -281,14 +285,14 @@ export default {
|
||||
{ userId },
|
||||
)
|
||||
const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user'))
|
||||
await images.deleteImage(user, 'AVATAR_IMAGE', { transaction })
|
||||
await images(context.config).deleteImage(user, 'AVATAR_IMAGE', { transaction })
|
||||
return user
|
||||
})
|
||||
try {
|
||||
const user = await deleteUserTxResultPromise
|
||||
return user
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
},
|
||||
switchUserRole: async (_object, args, context, _resolveInfo) => {
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
let authenticatedUser, mutate, query, variables
|
||||
let variables
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({
|
||||
authenticatedUser,
|
||||
})
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const updateUserMutation = gql`
|
||||
mutation ($id: ID!, $name: String!, $locationName: String) {
|
||||
@ -78,23 +84,19 @@ const newlyCreatedNodesWithLocales = [
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
const apolloSetup = createApolloTestSetup({
|
||||
context,
|
||||
})
|
||||
mutate = createTestClient(server).mutate
|
||||
query = createTestClient(server).query
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
afterAll(() => {
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@ -110,9 +112,8 @@ afterEach(async () => {
|
||||
describe('Location Service', () => {
|
||||
// Authentication
|
||||
// TODO: unify, externalize, simplify, wtf?
|
||||
let user
|
||||
beforeEach(async () => {
|
||||
user = await Factory.build('user', {
|
||||
const user = await Factory.build('user', {
|
||||
id: 'location-user',
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
@ -195,9 +196,8 @@ describe('Location Service', () => {
|
||||
|
||||
describe('userMiddleware', () => {
|
||||
describe('UpdateUser', () => {
|
||||
let user
|
||||
beforeEach(async () => {
|
||||
user = await Factory.build('user', {
|
||||
const user = await Factory.build('user', {
|
||||
id: 'updating-user',
|
||||
})
|
||||
authenticatedUser = await user.toJson()
|
||||
@ -211,7 +211,7 @@ describe('userMiddleware', () => {
|
||||
locationName: 'Welzheim, Baden-Württemberg, Germany',
|
||||
}
|
||||
await mutate({ mutation: updateUserMutation, variables })
|
||||
const locations = await neode.cypher(
|
||||
const locations = await database.neode.cypher(
|
||||
`MATCH (city:Location)-[:IS_IN]->(district:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city {.*}, state {.*}, country {.*}`,
|
||||
{},
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
/* eslint-disable n/no-unsupported-features/node-builtins */
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl', 'ru']
|
||||
|
||||
@ -61,7 +61,13 @@ const createLocation = async (session, mapboxData) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, session) => {
|
||||
export const createOrUpdateLocations = async (
|
||||
nodeLabel,
|
||||
nodeId,
|
||||
locationName,
|
||||
session,
|
||||
context: Context,
|
||||
) => {
|
||||
if (locationName === undefined) return
|
||||
|
||||
let locationId
|
||||
@ -72,7 +78,7 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
||||
locationName,
|
||||
)}.json?access_token=${
|
||||
CONFIG.MAPBOX_TOKEN
|
||||
context.config.MAPBOX_TOKEN
|
||||
}&types=region,place,country,address&language=${locales.join(',')}`,
|
||||
{
|
||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
||||
@ -156,10 +162,10 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s
|
||||
}
|
||||
}
|
||||
|
||||
export const queryLocations = async ({ place, lang }) => {
|
||||
export const queryLocations = async ({ place, lang }, context: Context) => {
|
||||
try {
|
||||
const res: any = await fetch(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`,
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${context.config.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`,
|
||||
{
|
||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
||||
},
|
||||
|
||||
@ -4,12 +4,20 @@
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import User from '@db/models/User'
|
||||
import { getDriver, getNeode } from '@db/neo4j'
|
||||
import { TEST_CONFIG } from '@root/test/helpers'
|
||||
|
||||
import decode from './decode'
|
||||
import encode from './encode'
|
||||
import { decode } from './decode'
|
||||
import { encode } from './encode'
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
const config = {
|
||||
JWT_SECRET: 'supersecret',
|
||||
JWT_EXPIRES: TEST_CONFIG.JWT_EXPIRES,
|
||||
CLIENT_URI: TEST_CONFIG.CLIENT_URI,
|
||||
GRAPHQL_URI: TEST_CONFIG.GRAPHQL_URI,
|
||||
}
|
||||
const context = { driver, config }
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
@ -26,9 +34,9 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('decode', () => {
|
||||
let authorizationHeader
|
||||
let authorizationHeader: string | undefined | null
|
||||
const returnsNull = async () => {
|
||||
await expect(decode(driver, authorizationHeader)).resolves.toBeNull()
|
||||
await expect(decode(context)(authorizationHeader)).resolves.toBeNull()
|
||||
}
|
||||
|
||||
describe('given `null` as JWT Bearer token', () => {
|
||||
@ -57,7 +65,8 @@ describe('decode', () => {
|
||||
|
||||
describe('given valid JWT Bearer token', () => {
|
||||
describe('and corresponding user in the database', () => {
|
||||
let user, validAuthorizationHeader
|
||||
let user
|
||||
let validAuthorizationHeader: string
|
||||
beforeEach(async () => {
|
||||
user = await Factory.build(
|
||||
'user',
|
||||
@ -74,11 +83,11 @@ describe('decode', () => {
|
||||
email: 'user@example.org',
|
||||
},
|
||||
)
|
||||
validAuthorizationHeader = encode(await user.toJson())
|
||||
validAuthorizationHeader = encode(context)(await user.toJson())
|
||||
})
|
||||
|
||||
it('returns user object without email', async () => {
|
||||
await expect(decode(driver, validAuthorizationHeader)).resolves.toMatchObject({
|
||||
await expect(decode(context)(validAuthorizationHeader)).resolves.toMatchObject({
|
||||
role: 'user',
|
||||
name: 'Jenny Rostock',
|
||||
id: 'u3',
|
||||
@ -89,7 +98,7 @@ describe('decode', () => {
|
||||
it('sets `lastActiveAt`', async () => {
|
||||
let user = await neode.first<typeof User>('User', { id: 'u3' }, undefined)
|
||||
await expect(user.toJson()).resolves.not.toHaveProperty('lastActiveAt')
|
||||
await decode(driver, validAuthorizationHeader)
|
||||
await decode(context)(validAuthorizationHeader)
|
||||
user = await neode.first<typeof User>('User', { id: 'u3' }, undefined)
|
||||
await expect(user.toJson()).resolves.toMatchObject({
|
||||
lastActiveAt: expect.any(String),
|
||||
@ -107,7 +116,7 @@ describe('decode', () => {
|
||||
await expect(user.toJson()).resolves.toMatchObject({
|
||||
lastActiveAt: '2019-10-03T23:33:08.598Z',
|
||||
})
|
||||
await decode(driver, validAuthorizationHeader)
|
||||
await decode(context)(validAuthorizationHeader)
|
||||
user = await neode.first<typeof User>('User', { id: 'u3' }, undefined)
|
||||
await expect(user.toJson()).resolves.toMatchObject({
|
||||
// should be a different time by now ;)
|
||||
|
||||
@ -1,44 +1,56 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { verify } from 'jsonwebtoken'
|
||||
|
||||
export default async (driver, authorizationHeader) => {
|
||||
if (!authorizationHeader) return null
|
||||
const token = authorizationHeader.replace('Bearer ', '')
|
||||
let id = null
|
||||
try {
|
||||
const decoded = await jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
id = decoded.sub
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
const session = driver.session()
|
||||
import type CONFIG from '@src/config'
|
||||
|
||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||
const updateUserLastActiveTransactionResponse = await transaction.run(
|
||||
`
|
||||
import type { JwtPayload } from 'jsonwebtoken'
|
||||
import type { Driver } from 'neo4j-driver'
|
||||
|
||||
export interface DecodedUser {
|
||||
id: string
|
||||
slug: string
|
||||
name: string
|
||||
role: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
const jwt = { verify }
|
||||
export const decode =
|
||||
(context: { config: Pick<typeof CONFIG, 'JWT_SECRET'>; driver: Driver }) =>
|
||||
async (authorizationHeader: string | undefined | null) => {
|
||||
if (!authorizationHeader) return null
|
||||
const token = authorizationHeader.replace('Bearer ', '')
|
||||
let id: null | string = null
|
||||
try {
|
||||
const decoded = jwt.verify(token, context.config.JWT_SECRET) as JwtPayload
|
||||
id = decoded.sub ?? null
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
const session = context.driver.session()
|
||||
|
||||
const writeTxResultPromise = session.writeTransaction<DecodedUser[]>(async (transaction) => {
|
||||
const updateUserLastActiveTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (user:User {id: $id, deleted: false, disabled: false })
|
||||
SET user.lastActiveAt = toString(datetime())
|
||||
RETURN user {.id, .slug, .name, .role, .disabled, .actorId}
|
||||
LIMIT 1
|
||||
`,
|
||||
{ id },
|
||||
)
|
||||
return updateUserLastActiveTransactionResponse.records.map((record) => record.get('user'))
|
||||
})
|
||||
try {
|
||||
const [currentUser] = await writeTxResultPromise
|
||||
if (!currentUser) return null
|
||||
return {
|
||||
token,
|
||||
...currentUser,
|
||||
{ id },
|
||||
)
|
||||
return updateUserLastActiveTransactionResponse.records.map((record) => record.get('user'))
|
||||
})
|
||||
try {
|
||||
const [currentUser] = await writeTxResultPromise
|
||||
if (!currentUser) return null
|
||||
return {
|
||||
token,
|
||||
...currentUser,
|
||||
}
|
||||
} finally {
|
||||
await session.close()
|
||||
}
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { verify } from 'jsonwebtoken'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import { TEST_CONFIG } from '@root/test/helpers'
|
||||
|
||||
import encode from './encode'
|
||||
import { encode } from './encode'
|
||||
|
||||
const jwt = { verify }
|
||||
const config = {
|
||||
JWT_SECRET: 'supersecret',
|
||||
JWT_EXPIRES: TEST_CONFIG.JWT_EXPIRES,
|
||||
CLIENT_URI: TEST_CONFIG.CLIENT_URI,
|
||||
GRAPHQL_URI: TEST_CONFIG.GRAPHQL_URI,
|
||||
}
|
||||
const context = { config }
|
||||
|
||||
describe('encode', () => {
|
||||
let payload
|
||||
@ -18,9 +25,9 @@ describe('encode', () => {
|
||||
})
|
||||
|
||||
it('encodes a valided JWT bearer token', () => {
|
||||
const token = encode(payload)
|
||||
const token = encode(context)(payload)
|
||||
expect(token.split('.')).toHaveLength(3)
|
||||
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
const decoded = jwt.verify(token, context.config.JWT_SECRET)
|
||||
expect(decoded).toEqual({
|
||||
name: 'Some body',
|
||||
slug: 'some-body',
|
||||
@ -43,7 +50,7 @@ describe('encode', () => {
|
||||
})
|
||||
|
||||
it('does not encode sensitive data', () => {
|
||||
const token = encode(payload)
|
||||
const token = encode(context)(payload)
|
||||
expect(payload).toEqual({
|
||||
email: 'none-of-your-business@example.org',
|
||||
password: 'topsecret',
|
||||
@ -51,7 +58,7 @@ describe('encode', () => {
|
||||
slug: 'some-body',
|
||||
id: 'some-id',
|
||||
})
|
||||
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
const decoded = jwt.verify(token, context.config.JWT_SECRET)
|
||||
expect(decoded).toEqual({
|
||||
name: 'Some body',
|
||||
slug: 'some-body',
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { sign } from 'jsonwebtoken'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import type CONFIG from '@src/config'
|
||||
|
||||
const jwt = { sign }
|
||||
// Generate an Access Token for the given User ID
|
||||
export default function encode(user) {
|
||||
const { id, name, slug } = user
|
||||
const token = jwt.sign({ id, name, slug }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES,
|
||||
issuer: CONFIG.GRAPHQL_URI,
|
||||
audience: CONFIG.CLIENT_URI,
|
||||
subject: user.id.toString(),
|
||||
})
|
||||
return token
|
||||
}
|
||||
export const encode =
|
||||
(context: {
|
||||
config: Pick<typeof CONFIG, 'JWT_SECRET' | 'JWT_EXPIRES' | 'GRAPHQL_URI' | 'CLIENT_URI'>
|
||||
}) =>
|
||||
(user) => {
|
||||
const { id, name, slug } = user
|
||||
const token: string = jwt.sign({ id, name, slug }, context.config.JWT_SECRET, {
|
||||
expiresIn: context.config.JWT_EXPIRES,
|
||||
issuer: context.config.GRAPHQL_URI,
|
||||
audience: context.config.CLIENT_URI,
|
||||
subject: user.id.toString(),
|
||||
})
|
||||
return token
|
||||
}
|
||||
|
||||
@ -1,33 +1,29 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import CONFIG from '@src/config'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import { categories } from '@src/constants/categories'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const database = databaseContext()
|
||||
let config: Partial<Context['config']>
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let server: ApolloServer
|
||||
let query
|
||||
beforeEach(() => {
|
||||
config = {}
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
const authenticatedUser = null
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
|
||||
const context = () => ({ config, authenticatedUser: null })
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
for (const category of categories) {
|
||||
await Factory.build('category', {
|
||||
id: category.id,
|
||||
@ -55,10 +51,10 @@ const categoriesQuery = gql`
|
||||
}
|
||||
`
|
||||
|
||||
describe('categroeis middleware', () => {
|
||||
describe('categories middleware', () => {
|
||||
describe('categories are active', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.CATEGORIES_ACTIVE = true
|
||||
config = { ...config, CATEGORIES_ACTIVE: true }
|
||||
})
|
||||
|
||||
it('returns the categories', async () => {
|
||||
@ -78,7 +74,7 @@ describe('categroeis middleware', () => {
|
||||
|
||||
describe('categories are not active', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
config = { ...config, CATEGORIES_ACTIVE: false }
|
||||
})
|
||||
|
||||
it('returns an empty array though there are categories in the db', async () => {
|
||||
|
||||
@ -1,9 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import CONFIG from '@src/config'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const checkCategoriesActive = (resolve, root, args, context, resolveInfo) => {
|
||||
if (CONFIG.CATEGORIES_ACTIVE) {
|
||||
type Resolver = (
|
||||
root: unknown,
|
||||
args: unknown,
|
||||
context: Context,
|
||||
resolveInfo: unknown,
|
||||
) => Promise<unknown>
|
||||
const checkCategoriesActive = (
|
||||
resolve: Resolver,
|
||||
root: unknown,
|
||||
args: unknown,
|
||||
context: Context,
|
||||
resolveInfo: unknown,
|
||||
) => {
|
||||
if (context.config.CATEGORIES_ACTIVE) {
|
||||
return resolve(root, args, context, resolveInfo)
|
||||
}
|
||||
return []
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let server
|
||||
let query
|
||||
let mutate
|
||||
let hashtagingUser
|
||||
let authenticatedUser
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
const categoryIds = ['cat9']
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
|
||||
@ -37,34 +36,27 @@ const updatePostMutation = gql`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
hashtagingUser = await neode.create('User', {
|
||||
hashtagingUser = await database.neode.create('User', {
|
||||
id: 'you',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
})
|
||||
await neode.create('Category', {
|
||||
await database.neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
|
||||
@ -16,7 +16,6 @@ import languages from './languages/languages'
|
||||
import login from './login/loginMiddleware'
|
||||
import notifications from './notifications/notificationsMiddleware'
|
||||
import orderBy from './orderByMiddleware'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import permissions from './permissionsMiddleware'
|
||||
import sentry from './sentryMiddleware'
|
||||
import sluggify from './sluggifyMiddleware'
|
||||
|
||||
@ -1,38 +1,33 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let variables
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
})
|
||||
mutate = createTestClient(server).mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
const createPostMutation = gql`
|
||||
|
||||
@ -1,27 +1,29 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser, emaillessMember
|
||||
let emaillessMember
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let postAuthor, groupMember
|
||||
|
||||
@ -94,21 +96,13 @@ const markAllAsRead = async () =>
|
||||
`,
|
||||
})
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let postAuthor, firstFollower, secondFollower, thirdFollower, emaillessFollower
|
||||
|
||||
@ -68,22 +70,13 @@ const followUserMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let postAuthor, groupMember, pendingMember, noMember, emaillessMember
|
||||
|
||||
@ -90,21 +91,13 @@ const markAllAsRead = async () =>
|
||||
`,
|
||||
})
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let postAuthor, firstCommenter, secondCommenter, emaillessObserver
|
||||
|
||||
@ -77,21 +77,13 @@ const toggleObservePostMutation = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -2,15 +2,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
@ -22,7 +19,12 @@ jest.mock('../helpers/isUserOnline', () => ({
|
||||
isUserOnline: () => isUserOnlineMock(),
|
||||
}))
|
||||
|
||||
let mutate, authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let postAuthor
|
||||
|
||||
@ -36,23 +38,17 @@ const createPostMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
const { server } = createServer({ context })
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
void server.stop()
|
||||
await database.driver.close()
|
||||
})
|
||||
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
|
||||
import CONFIG from '@src/config'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
jest.mock('@src/emails/sendEmail', () => ({
|
||||
sendNotificationMail: (notification) => sendNotificationMailMock(notification),
|
||||
}))
|
||||
|
||||
let query, mutate, authenticatedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const config = { CATEGORIES_ACTIVE: false }
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let postAuthor, groupMember, pendingMember, emaillessMember
|
||||
|
||||
@ -92,20 +93,13 @@ const markAllAsRead = async () =>
|
||||
`,
|
||||
})
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -4,11 +4,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import pubsubContext from '@context/pubsub'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
|
||||
@ -18,7 +15,10 @@ import { createRoomMutation } from '@graphql/queries/createRoomMutation'
|
||||
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
|
||||
import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
|
||||
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
import type { DecodedUser } from '@src/jwt/decode'
|
||||
|
||||
const sendChatMessageMailMock: (notification) => void = jest.fn()
|
||||
const sendNotificationMailMock: (notification) => void = jest.fn()
|
||||
@ -32,11 +32,17 @@ jest.mock('../helpers/isUserOnline', () => ({
|
||||
isUserOnline: () => isUserOnlineMock(),
|
||||
}))
|
||||
|
||||
const database = databaseContext()
|
||||
const pubsub = pubsubContext()
|
||||
const pubsubSpy = jest.spyOn(pubsub, 'publish')
|
||||
|
||||
let query, mutate, notifiedUser, authenticatedUser
|
||||
let notifiedUser
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser, pubsub })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
|
||||
let query: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const categoryIds = ['cat9']
|
||||
const createPostMutation = gql`
|
||||
@ -65,19 +71,13 @@ const createCommentMutation = gql`
|
||||
}
|
||||
`
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database, pubsub })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -910,7 +910,7 @@ describe('notifications', () => {
|
||||
userId: 'chatReceiver',
|
||||
},
|
||||
})
|
||||
roomId = room.data.CreateRoom.id
|
||||
roomId = (room.data as any).CreateRoom.id // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
})
|
||||
|
||||
describe('if the chatReceiver is online', () => {
|
||||
@ -1106,7 +1106,7 @@ describe('notifications', () => {
|
||||
|
||||
describe('user joins group', () => {
|
||||
const joinGroupAction = async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
authenticatedUser = (await notifiedUser.toJson()) as DecodedUser
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
@ -1193,7 +1193,7 @@ describe('notifications', () => {
|
||||
|
||||
describe('user joins and leaves group', () => {
|
||||
const leaveGroupAction = async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
authenticatedUser = (await notifiedUser.toJson()) as DecodedUser
|
||||
await mutate({
|
||||
mutation: leaveGroupMutation(),
|
||||
variables: {
|
||||
@ -1206,7 +1206,7 @@ describe('notifications', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
authenticatedUser = (await notifiedUser.toJson()) as DecodedUser
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
@ -1318,7 +1318,7 @@ describe('notifications', () => {
|
||||
|
||||
describe('user role in group changes', () => {
|
||||
const changeGroupMemberRoleAction = async () => {
|
||||
authenticatedUser = await groupOwner.toJson()
|
||||
authenticatedUser = (await groupOwner.toJson()) as DecodedUser
|
||||
await mutate({
|
||||
mutation: changeGroupMemberRoleMutation(),
|
||||
variables: {
|
||||
@ -1331,7 +1331,7 @@ describe('notifications', () => {
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
authenticatedUser = (await notifiedUser.toJson()) as DecodedUser
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
@ -1427,7 +1427,7 @@ describe('notifications', () => {
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await notifiedUser.toJson()
|
||||
authenticatedUser = (await notifiedUser.toJson()) as DecodedUser
|
||||
await mutate({
|
||||
mutation: joinGroupMutation(),
|
||||
variables: {
|
||||
|
||||
@ -1,36 +1,35 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import CONFIG from '@config/index'
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let variables
|
||||
let owner, anotherRegularUser, administrator, moderator
|
||||
|
||||
const database = databaseContext()
|
||||
let authenticatedUser: Context['user']
|
||||
let config: Partial<Context['config']>
|
||||
const context = () => ({ authenticatedUser, config })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
let server: ApolloServer
|
||||
let authenticatedUser
|
||||
let query, mutate
|
||||
beforeEach(() => {
|
||||
config = { CATEGORIES_ACTIVE: true }
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
mutate = apolloSetup.mutate
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@ -194,11 +193,16 @@ describe('authorization', () => {
|
||||
inviteCode: 'ABCDEF',
|
||||
locale: 'de',
|
||||
}
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
await Factory.build('inviteCode', {
|
||||
code: 'ABCDEF',
|
||||
})
|
||||
|
||||
config = {
|
||||
...config,
|
||||
CATEGORIES_ACTIVE: true,
|
||||
INVITE_REGISTRATION: false,
|
||||
PUBLIC_REGISTRATION: false,
|
||||
}
|
||||
})
|
||||
|
||||
describe('as user', () => {
|
||||
@ -237,11 +241,15 @@ describe('authorization', () => {
|
||||
inviteCode: 'ABCDEF',
|
||||
locale: 'de',
|
||||
}
|
||||
CONFIG.INVITE_REGISTRATION = false
|
||||
CONFIG.PUBLIC_REGISTRATION = true
|
||||
await Factory.build('inviteCode', {
|
||||
code: 'ABCDEF',
|
||||
})
|
||||
config = {
|
||||
...config,
|
||||
CATEGORIES_ACTIVE: true,
|
||||
INVITE_REGISTRATION: false,
|
||||
PUBLIC_REGISTRATION: true,
|
||||
}
|
||||
})
|
||||
|
||||
describe('as anyone', () => {
|
||||
@ -262,11 +270,15 @@ describe('authorization', () => {
|
||||
|
||||
describe('invite registration', () => {
|
||||
beforeEach(async () => {
|
||||
CONFIG.INVITE_REGISTRATION = true
|
||||
CONFIG.PUBLIC_REGISTRATION = false
|
||||
await Factory.build('inviteCode', {
|
||||
code: 'ABCDEF',
|
||||
})
|
||||
config = {
|
||||
...config,
|
||||
CATEGORIES_ACTIVE: true,
|
||||
INVITE_REGISTRATION: true,
|
||||
PUBLIC_REGISTRATION: false,
|
||||
}
|
||||
})
|
||||
|
||||
describe('as anyone with valid invite code', () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
@ -9,9 +9,8 @@ import { rule, shield, deny, allow, or, and } from 'graphql-shield'
|
||||
import CONFIG from '@config/index'
|
||||
import SocialMedia from '@db/models/SocialMedia'
|
||||
import { getNeode } from '@db/neo4j'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { validateInviteCode } from '@graphql/resolvers/inviteCodes'
|
||||
import { Context } from '@src/server'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const debug = !!CONFIG.DEBUG
|
||||
const allowExternalErrors = true
|
||||
@ -24,29 +23,29 @@ const isAuthenticated = rule({
|
||||
return !!ctx?.user?.id
|
||||
})
|
||||
|
||||
const isModerator = rule()(async (_parent, _args, { user }, _info) => {
|
||||
return user && (user.role === 'moderator' || user.role === 'admin')
|
||||
const isModerator = rule()(async (_parent, _args, { user }: Context, _info) => {
|
||||
return !!(user && (user.role === 'moderator' || user.role === 'admin'))
|
||||
})
|
||||
|
||||
const isAdmin = rule()(async (_parent, _args, { user }, _info) => {
|
||||
return user && user.role === 'admin'
|
||||
const isAdmin = rule()(async (_parent, _args, { user }: Context, _info) => {
|
||||
return !!(user && user.role === 'admin')
|
||||
})
|
||||
|
||||
const onlyYourself = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, context, _info) => {
|
||||
return context.user.id === args.id
|
||||
})(async (_parent, args, context: Context, _info) => {
|
||||
return context.user?.id === args.id
|
||||
})
|
||||
|
||||
const isMyOwn = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (parent, _args, { user }, _info) => {
|
||||
return user && user.id === parent.id
|
||||
})(async (parent, _args, { user }: Context, _info) => {
|
||||
return !!(user && user.id === parent.id)
|
||||
})
|
||||
|
||||
const isMySocialMedia = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_, args, { user }) => {
|
||||
})(async (_, args, { user }: Context) => {
|
||||
// We need a User
|
||||
if (!user) {
|
||||
return false
|
||||
@ -65,7 +64,7 @@ const isMySocialMedia = rule({
|
||||
|
||||
const isAllowedToChangeGroupSettings = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const ownerId = user.id
|
||||
const { id: groupId } = args
|
||||
@ -89,13 +88,13 @@ const isAllowedToChangeGroupSettings = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedSeeingGroupMembers = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const { id: groupId } = args
|
||||
const session = driver.session()
|
||||
@ -125,13 +124,13 @@ const isAllowedSeeingGroupMembers = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedToChangeGroupMemberRole = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const currentUserId = user.id
|
||||
const { groupId, userId, roleInGroup } = args
|
||||
@ -172,13 +171,13 @@ const isAllowedToChangeGroupMemberRole = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedToJoinGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const { groupId, userId } = args
|
||||
const session = driver.session()
|
||||
@ -202,13 +201,13 @@ const isAllowedToJoinGroup = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAllowedToLeaveGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const { groupId, userId } = args
|
||||
if (user.id !== userId) return false
|
||||
@ -232,13 +231,13 @@ const isAllowedToLeaveGroup = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isMemberOfGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const { groupId } = args
|
||||
if (!groupId) return true
|
||||
@ -260,13 +259,13 @@ const isMemberOfGroup = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const canRemoveUserFromGroup = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const { groupId, userId } = args
|
||||
const currentUserId = user.id
|
||||
@ -296,13 +295,13 @@ const canRemoveUserFromGroup = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const canCommentPost = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user?.id) return false
|
||||
const { postId } = args
|
||||
const userId = user.id
|
||||
@ -330,13 +329,13 @@ const canCommentPost = rule({
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isAuthor = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, { user, driver }) => {
|
||||
})(async (_parent, args, { user, driver }: Context) => {
|
||||
if (!user) return false
|
||||
const { id: resourceId } = args
|
||||
const session = driver.session()
|
||||
@ -354,14 +353,14 @@ const isAuthor = rule({
|
||||
const [author] = await authorReadTxPromise
|
||||
return !!author
|
||||
} finally {
|
||||
session.close()
|
||||
await session.close()
|
||||
}
|
||||
})
|
||||
|
||||
const isDeletingOwnAccount = rule({
|
||||
cache: 'no_cache',
|
||||
})(async (_parent, args, context, _info) => {
|
||||
return context.user.id === args.id
|
||||
})(async (_parent, args, context: Context, _info) => {
|
||||
return context.user?.id === args.id
|
||||
})
|
||||
|
||||
const noEmailFilter = rule({
|
||||
@ -370,10 +369,12 @@ const noEmailFilter = rule({
|
||||
return !('email' in args)
|
||||
})
|
||||
|
||||
const publicRegistration = rule()(() => CONFIG.PUBLIC_REGISTRATION)
|
||||
const publicRegistration = rule()(
|
||||
async (_parent, _args, context: Context) => context.config.PUBLIC_REGISTRATION,
|
||||
)
|
||||
|
||||
const inviteRegistration = rule()(async (_parent, args, context: Context) => {
|
||||
if (!CONFIG.INVITE_REGISTRATION) return false
|
||||
if (!context.config.INVITE_REGISTRATION) return false
|
||||
const { inviteCode } = args
|
||||
return validateInviteCode(context, inviteCode)
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import type { Context } from '@src/server'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
import uniqueSlug from './slugify/uniqueSlug'
|
||||
|
||||
|
||||
@ -2,16 +2,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
|
||||
import { createPostMutation } from '@graphql/queries/createPostMutation'
|
||||
import { signupVerificationMutation } from '@graphql/queries/signupVerificationMutation'
|
||||
import { updateGroupMutation } from '@graphql/queries/updateGroupMutation'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
let variables
|
||||
const categoryIds = ['cat9']
|
||||
@ -19,23 +17,18 @@ const categoryIds = ['cat9']
|
||||
const descriptionAdditional100 =
|
||||
' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789'
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
let authenticatedUser
|
||||
let mutate
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
mutate = createTestClientResult.mutate
|
||||
const testServer = createApolloTestSetup({ context })
|
||||
mutate = testServer.mutate
|
||||
database = testServer.database
|
||||
server = testServer.server
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
|
||||
@ -1,21 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/await-thenable */
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import { getNeode, getDriver } from '@db/neo4j'
|
||||
import createServer from '@src/server'
|
||||
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
|
||||
const categoryIds = ['cat9']
|
||||
let query, graphqlQuery, authenticatedUser, user, moderator, troll
|
||||
let graphqlQuery
|
||||
let moderator
|
||||
let user
|
||||
let troll
|
||||
let authenticatedUser: Context['user']
|
||||
const context = () => ({ authenticatedUser })
|
||||
let query: ApolloTestSetup['query']
|
||||
let database: ApolloTestSetup['database']
|
||||
let server: ApolloTestSetup['server']
|
||||
|
||||
const action = () => {
|
||||
return query({ query: graphqlQuery })
|
||||
@ -23,8 +27,15 @@ const action = () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
const apolloSetup = createApolloTestSetup({ context })
|
||||
query = apolloSetup.query
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
|
||||
// For performance reasons we do this only once
|
||||
const avatar = await Factory.build('image', {
|
||||
url: 'http://localhost/some/offensive/avatar.jpg',
|
||||
})
|
||||
const users = await Promise.all([
|
||||
Factory.build('user', { id: 'u1', role: 'user' }),
|
||||
Factory.build(
|
||||
@ -47,12 +58,10 @@ beforeAll(async () => {
|
||||
about: 'This self description is very offensive',
|
||||
},
|
||||
{
|
||||
avatar: Factory.build('image', {
|
||||
url: 'http://localhost/some/offensive/avatar.jpg',
|
||||
}),
|
||||
avatar,
|
||||
},
|
||||
),
|
||||
neode.create('Category', {
|
||||
database.neode.create('Category', {
|
||||
id: 'cat9',
|
||||
name: 'Democracy & Politics',
|
||||
icon: 'university',
|
||||
@ -136,18 +145,6 @@ beforeAll(async () => {
|
||||
),
|
||||
])
|
||||
|
||||
const { server } = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
driver,
|
||||
neode,
|
||||
user: authenticatedUser,
|
||||
}
|
||||
},
|
||||
})
|
||||
const client = createTestClient(server)
|
||||
query = client.query
|
||||
|
||||
const trollingPost = resources[1]
|
||||
const trollingComment = resources[2]
|
||||
|
||||
@ -202,7 +199,9 @@ beforeAll(async () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
await driver.close()
|
||||
void server.stop()
|
||||
void database.driver.close()
|
||||
database.neode.close()
|
||||
})
|
||||
|
||||
describe('softDeleteMiddleware', () => {
|
||||
@ -222,7 +221,7 @@ describe('softDeleteMiddleware', () => {
|
||||
}
|
||||
`
|
||||
const { data } = await action()
|
||||
subject = data.User[0].following[0].comments[0]
|
||||
subject = (data as any).User[0].following[0].comments[0]
|
||||
}
|
||||
const beforeUser = async () => {
|
||||
graphqlQuery = gql`
|
||||
@ -240,7 +239,7 @@ describe('softDeleteMiddleware', () => {
|
||||
}
|
||||
`
|
||||
const { data } = await action()
|
||||
subject = data.User[0].following[0]
|
||||
subject = (data as any).User[0].following[0]
|
||||
}
|
||||
const beforePost = async () => {
|
||||
graphqlQuery = gql`
|
||||
@ -261,7 +260,7 @@ describe('softDeleteMiddleware', () => {
|
||||
}
|
||||
`
|
||||
const { data } = await action()
|
||||
subject = data.User[0].following[0].contributions[0]
|
||||
subject = (data as any).User[0].following[0].contributions[0]
|
||||
}
|
||||
|
||||
describe('as moderator', () => {
|
||||
@ -276,10 +275,11 @@ describe('softDeleteMiddleware', () => {
|
||||
it('displays slug', () => expect(subject.slug).toEqual('offensive-name'))
|
||||
it('displays about', () =>
|
||||
expect(subject.about).toEqual('This self description is very offensive'))
|
||||
it('displays avatar', () =>
|
||||
it('displays avatar', async () => {
|
||||
expect(subject.avatar).toEqual({
|
||||
url: expect.stringMatching('http://localhost/some/offensive/avatar.jpg'),
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Post', () => {
|
||||
@ -369,10 +369,9 @@ describe('softDeleteMiddleware', () => {
|
||||
|
||||
it('shows disabled but hides deleted posts', async () => {
|
||||
const expected = [{ title: 'Disabled post' }, { title: 'Publicly visible post' }]
|
||||
const {
|
||||
data: { Post },
|
||||
} = await action()
|
||||
await expect(Post).toEqual(expect.arrayContaining(expected))
|
||||
const { data } = await action()
|
||||
const { Post } = data as any
|
||||
expect(Post).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
|
||||
@ -400,12 +399,11 @@ describe('softDeleteMiddleware', () => {
|
||||
{ content: 'Enabled comment on public post' },
|
||||
{ content: 'UNAVAILABLE' },
|
||||
]
|
||||
const { data } = await action()
|
||||
const {
|
||||
data: {
|
||||
Post: [{ comments }],
|
||||
},
|
||||
} = await action()
|
||||
await expect(comments).toEqual(expect.arrayContaining(expected))
|
||||
Post: [{ comments }],
|
||||
} = data as any
|
||||
expect(comments).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
|
||||
@ -419,12 +417,11 @@ describe('softDeleteMiddleware', () => {
|
||||
{ content: 'Enabled comment on public post' },
|
||||
{ content: 'Disabled comment' },
|
||||
]
|
||||
const { data } = await action()
|
||||
const {
|
||||
data: {
|
||||
Post: [{ comments }],
|
||||
},
|
||||
} = await action()
|
||||
await expect(comments).toEqual(expect.arrayContaining(expected))
|
||||
Post: [{ comments }],
|
||||
} = data as any
|
||||
expect(comments).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,33 +1,29 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
|
||||
import Factory, { cleanDatabase } from '@db/factories'
|
||||
import type { ApolloTestSetup } from '@root/test/helpers'
|
||||
import { createApolloTestSetup } from '@root/test/helpers'
|
||||
import type { Context } from '@src/context'
|
||||
import { loginMutation } from '@src/graphql/queries/loginMutation'
|
||||
import ocelotLogger from '@src/logger'
|
||||
import { loggerPlugin } from '@src/plugins/apolloLogger'
|
||||
import createServer, { getContext } from '@src/server'
|
||||
|
||||
const database = databaseContext()
|
||||
|
||||
let server: ApolloServer
|
||||
|
||||
let mutate, authenticatedUser
|
||||
const authenticatedUser: Context['user'] = null
|
||||
let mutate: ApolloTestSetup['mutate']
|
||||
let database: ApolloTestSetup['database']
|
||||
const context = () => ({ authenticatedUser })
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const contextUser = async (_req) => authenticatedUser
|
||||
const context = getContext({ user: contextUser, database })
|
||||
|
||||
server = createServer({ context, plugins: [loggerPlugin] }).server
|
||||
|
||||
const createTestClientResult = createTestClient(server)
|
||||
mutate = createTestClientResult.mutate
|
||||
const apolloSetup = createApolloTestSetup({ context, plugins: [loggerPlugin] })
|
||||
mutate = apolloSetup.mutate
|
||||
database = apolloSetup.database
|
||||
server = apolloSetup.server
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -61,7 +57,7 @@ describe('apollo logger', () => {
|
||||
})
|
||||
|
||||
describe('login mutation', () => {
|
||||
it('logs the request and response', async () => {
|
||||
it('logs the request and response, masking password and token', async () => {
|
||||
await mutate({
|
||||
mutation: loginMutation,
|
||||
variables: {
|
||||
@ -81,7 +77,7 @@ describe('apollo logger', () => {
|
||||
}),
|
||||
)
|
||||
|
||||
expect(loggerSpy).toBeCalledWith('Apollo Response', expect.any(String), expect.any(String))
|
||||
expect(loggerSpy).toBeCalledWith('Apollo Response', expect.any(String), '{"login":"token"}')
|
||||
|
||||
expect(consoleSpy).toBeCalledTimes(2)
|
||||
})
|
||||
|
||||
@ -30,7 +30,14 @@ export const loggerPlugin = {
|
||||
ocelotLogger.error(...logResponse, JSON.stringify(requestContext.errors))
|
||||
return
|
||||
}
|
||||
logResponse.push(JSON.stringify(requestContext.response.data))
|
||||
if (requestContext.response.data.login) {
|
||||
// mask the token
|
||||
const data = cloneDeep(requestContext.response.data)
|
||||
data.login = 'token'
|
||||
logResponse.push(JSON.stringify(data))
|
||||
} else {
|
||||
logResponse.push(JSON.stringify(requestContext.response.data))
|
||||
}
|
||||
ocelotLogger.debug(...logResponse)
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable import/no-named-as-default-member */
|
||||
import http from 'node:http'
|
||||
@ -13,86 +12,31 @@ import express from 'express'
|
||||
import { graphqlUploadExpress } from 'graphql-upload'
|
||||
import helmet from 'helmet'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import pubsubContext from '@context/pubsub'
|
||||
|
||||
import CONFIG from './config'
|
||||
import { context, getContext } from './context'
|
||||
import schema from './graphql/schema'
|
||||
import decode from './jwt/decode'
|
||||
import ocelotLogger from './logger'
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import middleware from './middleware'
|
||||
|
||||
import type OcelotLogger from './logger'
|
||||
import type { ApolloServerExpressConfig } from 'apollo-server-express'
|
||||
|
||||
const serverDatabase = databaseContext()
|
||||
const serverPubsub = pubsubContext()
|
||||
|
||||
const databaseUser = async (req) => decode(serverDatabase.driver, req.headers.authorization)
|
||||
|
||||
export const getContext =
|
||||
(
|
||||
{
|
||||
database = serverDatabase,
|
||||
pubsub = serverPubsub,
|
||||
user = databaseUser,
|
||||
logger = ocelotLogger,
|
||||
}: {
|
||||
database?: ReturnType<typeof databaseContext>
|
||||
pubsub?: ReturnType<typeof pubsubContext>
|
||||
user?: (any) => Promise<any>
|
||||
logger?: typeof OcelotLogger
|
||||
} = {
|
||||
database: serverDatabase,
|
||||
pubsub: serverPubsub,
|
||||
user: databaseUser,
|
||||
logger: ocelotLogger,
|
||||
},
|
||||
) =>
|
||||
async (req) => {
|
||||
const u = await user(req)
|
||||
return {
|
||||
database,
|
||||
driver: database.driver,
|
||||
neode: database.neode,
|
||||
pubsub,
|
||||
logger,
|
||||
user: u,
|
||||
req,
|
||||
cypherParams: {
|
||||
currentUserId: u ? u.id : null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type Context = Awaited<ReturnType<ReturnType<typeof getContext>>>
|
||||
|
||||
export const context = async (options) => {
|
||||
const { connection, req } = options
|
||||
if (connection) {
|
||||
return connection.context
|
||||
} else {
|
||||
return getContext()(req)
|
||||
}
|
||||
}
|
||||
|
||||
const createServer = (options?) => {
|
||||
const defaults = {
|
||||
const createServer = (options?: ApolloServerExpressConfig) => {
|
||||
const defaults: ApolloServerExpressConfig = {
|
||||
context,
|
||||
schema: middleware(schema),
|
||||
subscriptions: {
|
||||
onConnect: (connectionParams) => getContext()(connectionParams),
|
||||
onConnect: (connectionParams) =>
|
||||
getContext()(connectionParams as { headers: { authorization?: string } }),
|
||||
},
|
||||
debug: !!CONFIG.DEBUG,
|
||||
uploads: false,
|
||||
tracing: !!CONFIG.DEBUG,
|
||||
formatError: (error) => {
|
||||
// console.log(error.originalError)
|
||||
if (error.message === 'ERROR_VALIDATION') {
|
||||
return new Error(error.originalError.details.map((d) => d.message))
|
||||
return new Error((error.originalError as any).details.map((d) => d.message))
|
||||
}
|
||||
return error
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
const server = new ApolloServer(Object.assign(defaults, options))
|
||||
|
||||
|
||||
70
backend/src/uploads/s3Service.ts
Normal file
70
backend/src/uploads/s3Service.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { S3Client, DeleteObjectCommand, ObjectCannedACL } from '@aws-sdk/client-s3'
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
|
||||
import type { S3Config } from '@config/index'
|
||||
|
||||
import { FileUploadCallback, FileDeleteCallback } from './types'
|
||||
|
||||
export const s3Service = (config: S3Config, prefix: string) => {
|
||||
const { AWS_BUCKET: Bucket } = config
|
||||
|
||||
const { AWS_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_PUBLIC_GATEWAY } = config
|
||||
const s3 = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
endpoint: AWS_ENDPOINT,
|
||||
forcePathStyle: true,
|
||||
})
|
||||
|
||||
const uploadFile: FileUploadCallback = async ({ createReadStream, uniqueFilename, mimetype }) => {
|
||||
const s3Location = prefix.length > 0 ? `${prefix}/${uniqueFilename}` : uniqueFilename
|
||||
|
||||
const params = {
|
||||
Bucket,
|
||||
Key: s3Location,
|
||||
ACL: ObjectCannedACL.public_read,
|
||||
ContentType: mimetype,
|
||||
Body: createReadStream(),
|
||||
}
|
||||
const command = new Upload({ client: s3, params })
|
||||
const data = await command.done()
|
||||
let { Location: location } = data
|
||||
if (!location) {
|
||||
throw new Error('File upload did not return `Location`')
|
||||
}
|
||||
|
||||
if (!location.startsWith('https://') && !location.startsWith('http://')) {
|
||||
// Ensure the location has a protocol. Hetzner sometimes does not return a protocol in the location.
|
||||
location = `https://${location}`
|
||||
}
|
||||
|
||||
if (!S3_PUBLIC_GATEWAY) {
|
||||
return location
|
||||
}
|
||||
|
||||
const publicLocation = new URL(S3_PUBLIC_GATEWAY)
|
||||
publicLocation.pathname = new URL(location).pathname
|
||||
return publicLocation.href
|
||||
}
|
||||
|
||||
const deleteFile: FileDeleteCallback = async (url) => {
|
||||
let { pathname } = new URL(url, 'http://example.org') // dummy domain to avoid invalid URL error
|
||||
pathname = pathname.substring(1) // remove first character '/'
|
||||
const prefix = `${Bucket}/`
|
||||
if (pathname.startsWith(prefix)) {
|
||||
pathname = pathname.slice(prefix.length)
|
||||
}
|
||||
const params = {
|
||||
Bucket,
|
||||
Key: pathname,
|
||||
}
|
||||
await s3.send(new DeleteObjectCommand(params))
|
||||
}
|
||||
|
||||
return {
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
}
|
||||
}
|
||||
7
backend/src/uploads/types.ts
Normal file
7
backend/src/uploads/types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { FileUpload } from 'graphql-upload'
|
||||
|
||||
export type FileDeleteCallback = (url: string) => Promise<void>
|
||||
|
||||
export type FileUploadCallback = (
|
||||
upload: Pick<FileUpload, 'createReadStream' | 'mimetype'> & { uniqueFilename: string },
|
||||
) => Promise<string>
|
||||
101
backend/test/helpers.ts
Normal file
101
backend/test/helpers.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import databaseContext from '@context/database'
|
||||
import type CONFIG from '@src/config'
|
||||
import type { Context } from '@src/context'
|
||||
import { getContext } from '@src/context'
|
||||
import createServer from '@src/server'
|
||||
|
||||
import type { ApolloServerExpressConfig } from 'apollo-server-express'
|
||||
|
||||
export const TEST_CONFIG = {
|
||||
NODE_ENV: 'test',
|
||||
DEBUG: undefined,
|
||||
TEST: true,
|
||||
PRODUCTION: false,
|
||||
PRODUCTION_DB_CLEAN_ALLOW: false,
|
||||
DISABLED_MIDDLEWARES: [],
|
||||
SEND_MAIL: false,
|
||||
|
||||
CLIENT_URI: 'http://webapp:3000',
|
||||
GRAPHQL_URI: 'http://localhost:4000',
|
||||
JWT_EXPIRES: '2y',
|
||||
|
||||
MAPBOX_TOKEN:
|
||||
'pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g',
|
||||
JWT_SECRET: 'JWT_SECRET',
|
||||
PRIVATE_KEY_PASSPHRASE: 'PRIVATE_KEY_PASSPHRASE',
|
||||
|
||||
NEO4J_URI: 'bolt://localhost:7687',
|
||||
NEO4J_USERNAME: 'neo4j',
|
||||
NEO4J_PASSWORD: 'neo4j',
|
||||
|
||||
SENTRY_DSN_BACKEND: undefined,
|
||||
COMMIT: undefined,
|
||||
|
||||
REDIS_DOMAIN: undefined,
|
||||
REDIS_PORT: undefined,
|
||||
REDIS_PASSWORD: undefined,
|
||||
|
||||
AWS_ACCESS_KEY_ID: 'minio',
|
||||
AWS_SECRET_ACCESS_KEY: '12341234',
|
||||
AWS_ENDPOINT: 'http:/minio:9000',
|
||||
AWS_REGION: 'local',
|
||||
AWS_BUCKET: 'ocelot',
|
||||
|
||||
S3_PUBLIC_GATEWAY: undefined,
|
||||
|
||||
EMAIL_DEFAULT_SENDER: '',
|
||||
SUPPORT_EMAIL: '',
|
||||
SUPPORT_URL: '',
|
||||
APPLICATION_NAME: '',
|
||||
ORGANIZATION_URL: '',
|
||||
PUBLIC_REGISTRATION: false,
|
||||
INVITE_REGISTRATION: true,
|
||||
INVITE_CODES_PERSONAL_PER_USER: 7,
|
||||
INVITE_CODES_GROUP_PER_USER: 7,
|
||||
CATEGORIES_ACTIVE: false,
|
||||
MAX_PINNED_POSTS: 1,
|
||||
|
||||
LANGUAGE_DEFAULT: 'en',
|
||||
LOG_LEVEL: 'DEBUG',
|
||||
} as const satisfies typeof CONFIG
|
||||
|
||||
interface OverwritableContextParams {
|
||||
authenticatedUser?: Context['user']
|
||||
config?: Partial<typeof CONFIG>
|
||||
pubsub?: Context['pubsub']
|
||||
}
|
||||
interface CreateTestServerOptions {
|
||||
context: () => OverwritableContextParams | Promise<OverwritableContextParams>
|
||||
plugins?: ApolloServerExpressConfig['plugins']
|
||||
}
|
||||
|
||||
export const createApolloTestSetup = (opts?: CreateTestServerOptions) => {
|
||||
const defaultOpts: CreateTestServerOptions = { context: () => ({ authenticatedUser: null }) }
|
||||
const { context: testContext, plugins } = opts ?? defaultOpts
|
||||
const database = databaseContext()
|
||||
const context = async (req: { headers: { authorization?: string } }) => {
|
||||
const { authenticatedUser, config = {}, pubsub } = await testContext()
|
||||
return getContext({
|
||||
authenticatedUser,
|
||||
database,
|
||||
pubsub,
|
||||
config: { ...TEST_CONFIG, ...config },
|
||||
})(req)
|
||||
}
|
||||
|
||||
const server = createServer({
|
||||
context,
|
||||
plugins,
|
||||
}).server
|
||||
const { mutate, query } = createTestClient(server)
|
||||
return {
|
||||
server,
|
||||
query,
|
||||
mutate,
|
||||
database,
|
||||
}
|
||||
}
|
||||
|
||||
export type ApolloTestSetup = ReturnType<typeof createApolloTestSetup>
|
||||
2052
backend/yarn.lock
2052
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,8 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
|
||||
defineStep('I should see my comment', () => {
|
||||
cy.get('article.comment-card p')
|
||||
.should('contain', 'Ocelot.social rocks')
|
||||
.get('.user-teaser span.slug')
|
||||
.should('contain', '@peter-pan') // specific enough
|
||||
.get('.user-teaser span.name')
|
||||
.should('contain', 'Peter Pan') // specific enough
|
||||
.get('.profile-avatar img')
|
||||
.should('have.attr', 'src')
|
||||
.and('contain', 'https://') // some url
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import encode from '../../../../backend/build/src/jwt/encode'
|
||||
import CONFIG from '../../../../backend/build/src/config/index'
|
||||
import { encode } from '../../../../backend/build/src/jwt/encode'
|
||||
|
||||
defineStep('I am logged in as {string}', slug => {
|
||||
cy.neode()
|
||||
@ -13,6 +14,6 @@ defineStep('I am logged in as {string}', slug => {
|
||||
})
|
||||
})
|
||||
.then(user => {
|
||||
cy.setCookie('ocelot-social-token', encode(user))
|
||||
cy.setCookie('ocelot-social-token', encode({ config: CONFIG })(user))
|
||||
})
|
||||
})
|
||||
|
||||
@ -21,4 +21,4 @@ version: 0.1.0
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "3.10.1"
|
||||
appVersion: "3.11.0"
|
||||
|
||||
@ -21,4 +21,4 @@ version: 0.1.0
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "3.10.1"
|
||||
appVersion: "3.11.0"
|
||||
|
||||
@ -32,7 +32,6 @@ services:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
- DEBUG=true
|
||||
- SMTP_PORT=1025
|
||||
- SMTP_HOST=mailserver
|
||||
- AWS_ACCESS_KEY_ID=minio
|
||||
@ -41,7 +40,6 @@ services:
|
||||
- AWS_REGION=local
|
||||
- AWS_BUCKET=ocelot
|
||||
- S3_PUBLIC_GATEWAY=http:/localhost:9000
|
||||
- DEBUG=neo4j-graphql-js
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ services:
|
||||
- AWS_ENDPOINT=http:/minio:9000
|
||||
- AWS_REGION=local
|
||||
- AWS_BUCKET=ocelot
|
||||
- DEBUG=
|
||||
volumes:
|
||||
- ./coverage:/app/coverage
|
||||
|
||||
@ -35,7 +36,10 @@ services:
|
||||
|
||||
neo4j:
|
||||
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||
image: ghcr.io/ocelot-social-community/ocelot-social/neo4j-community:test
|
||||
image: ghcr.io/ocelot-social-community/ocelot-social/neo4j:community
|
||||
build:
|
||||
context: ./neo4j
|
||||
target: community
|
||||
#environment:
|
||||
# - NEO4J_dbms_connector_bolt_enabled=true
|
||||
# - NEO4J_dbms_connector_bolt_tls__level=OPTIONAL
|
||||
|
||||
@ -59,7 +59,6 @@ services:
|
||||
# - PORT="4000"
|
||||
- NODE_ENV="production"
|
||||
# Application only envs
|
||||
- DEBUG=false
|
||||
- NEO4J_URI=bolt://neo4j:7687
|
||||
- GRAPHQL_URI=http://backend:4000
|
||||
- CLIENT_URI=http://webapp:3000
|
||||
@ -75,7 +74,7 @@ services:
|
||||
- 3001:80
|
||||
|
||||
neo4j:
|
||||
image: ghcr.io/ocelot-social-community/ocelot-social/neo4j
|
||||
image: ghcr.io/ocelot-social-community/ocelot-social/neo4j:community
|
||||
build:
|
||||
context: ./neo4j
|
||||
# community edition 👆🏼, because we have no enterprise licence 👇🏼 at the moment
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ocelot-social-frontend",
|
||||
"version": "3.10.0",
|
||||
"version": "3.11.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ocelot-social-frontend",
|
||||
"version": "3.10.0",
|
||||
"version": "3.11.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-frontend",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"description": "ocelot.social new Frontend (in development and not fully implemented) by IT4C Boilerplate for frontends",
|
||||
"main": "build/index.js",
|
||||
"type": "module",
|
||||
|
||||
508
package-lock.json
generated
508
package-lock.json
generated
@ -1,29 +1,28 @@
|
||||
{
|
||||
"name": "ocelot-social",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ocelot-social",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.1",
|
||||
"@babel/register": "^7.27.1",
|
||||
"@badeball/cypress-cucumber-preprocessor": "^22.1.0",
|
||||
"@badeball/cypress-cucumber-preprocessor": "^22.2.0",
|
||||
"@cucumber/cucumber": "11.3.0",
|
||||
"@cypress/browserify-preprocessor": "^3.0.2",
|
||||
"@faker-js/faker": "9.8.0",
|
||||
"@faker-js/faker": "9.9.0",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^14.5.0",
|
||||
"cypress": "^14.5.1",
|
||||
"cypress-network-idle": "^1.15.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"expect": "^29.6.4",
|
||||
"dotenv": "^17.2.0",
|
||||
"graphql-request": "^2.0.0",
|
||||
"import": "^0.0.6",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
@ -1825,9 +1824,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@badeball/cypress-cucumber-preprocessor": {
|
||||
"version": "22.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@badeball/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-22.1.0.tgz",
|
||||
"integrity": "sha512-6hZdi7krImbUKp8lVqwMcW5cGxMmyZahPpkWL5D3wfTYRRhVgeOx9Y7liyCqKcOZkyCoufJEx8iGtTNYKiR3SQ==",
|
||||
"version": "22.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@badeball/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-22.2.0.tgz",
|
||||
"integrity": "sha512-od4a1k5VeptXSr1AI2gi5iHMmrKQhwXeLouiuv1yF6Th/FoDstaukdPy6lvwqAuEgb4wx0H1eFVi5/rlSD+1pA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1836,7 +1835,6 @@
|
||||
}
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cucumber/ci-environment": "^10.0.1",
|
||||
"@cucumber/cucumber": "^11.0.0",
|
||||
@ -2848,9 +2846,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@faker-js/faker": {
|
||||
"version": "9.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.8.0.tgz",
|
||||
"integrity": "sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==",
|
||||
"version": "9.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.9.0.tgz",
|
||||
"integrity": "sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -2858,7 +2856,6 @@
|
||||
"url": "https://opencollective.com/fakerjs"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
@ -2984,90 +2981,6 @@
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/expect-utils": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
|
||||
"integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jest-get-type": "^29.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/schemas": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
|
||||
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.27.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/types": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
|
||||
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/schemas": "^29.6.3",
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^17.0.8",
|
||||
"chalk": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/types/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/types/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/types/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
@ -4249,12 +4162,6 @@
|
||||
"integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sindresorhus/merge-streams": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
|
||||
@ -4322,30 +4229,6 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-report": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
|
||||
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/istanbul-reports": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
|
||||
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsonfile": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
|
||||
@ -4411,7 +4294,7 @@
|
||||
"version": "18.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz",
|
||||
"integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==",
|
||||
"devOptional": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
@ -4444,12 +4327,6 @@
|
||||
"integrity": "sha512-m04Om5Gz6kbjUwAQ7XJJQ30OdEFsSmAVsvn4NYwcTRyMVpKKa1aPuESw1n2CxS5fYkOQv3nHgDKeNa8e76fUkw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stack-utils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
|
||||
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
@ -4474,21 +4351,6 @@
|
||||
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz",
|
||||
"integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yargs-parser": {
|
||||
"version": "21.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
|
||||
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
@ -7237,21 +7099,6 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/sibiraj-s"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cipher-base": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
||||
@ -7825,12 +7672,11 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "14.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.0.tgz",
|
||||
"integrity": "sha512-1HOnKvWep0LkWuFwPeWkZ0TDl7ivi2/Mz+WNU4dfkeLJaFndS3Ow6TXT7YjuTqLFI2peJKzPKljVUFdymI2K5g==",
|
||||
"version": "14.5.1",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.1.tgz",
|
||||
"integrity": "sha512-vYBeZKW3UAtxwv5mFuSlOBCYhyO0H86TeDKRJ7TgARyHiREIaiDjeHtqjzrXRFrdz9KnNavqlm+z+hklC7v8XQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cypress/request": "^3.0.8",
|
||||
"@cypress/xvfb": "^1.2.4",
|
||||
@ -8446,15 +8292,6 @@
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-sequences": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||
"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
@ -8556,9 +8393,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
||||
"version": "17.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz",
|
||||
"integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
@ -9021,22 +8858,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/expect": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
|
||||
"integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/expect-utils": "^29.7.0",
|
||||
"jest-get-type": "^29.6.3",
|
||||
"jest-matcher-utils": "^29.7.0",
|
||||
"jest-message-util": "^29.7.0",
|
||||
"jest-util": "^29.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
@ -10921,254 +10742,6 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
|
||||
"integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^29.6.3",
|
||||
"jest-get-type": "^29.6.3",
|
||||
"pretty-format": "^29.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-get-type": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
|
||||
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-matcher-utils": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
|
||||
"integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^29.7.0",
|
||||
"jest-get-type": "^29.6.3",
|
||||
"pretty-format": "^29.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-matcher-utils/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-matcher-utils/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-matcher-utils/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-message-util": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
|
||||
"integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@jest/types": "^29.6.3",
|
||||
"@types/stack-utils": "^2.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"micromatch": "^4.0.4",
|
||||
"pretty-format": "^29.7.0",
|
||||
"slash": "^3.0.0",
|
||||
"stack-utils": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-message-util/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-message-util/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-message-util/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-util": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
|
||||
"integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^29.6.3",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"ci-info": "^3.2.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"picomatch": "^2.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-util/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-util/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-util/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -13590,20 +13163,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/schemas": "^29.6.3",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^18.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-ms": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz",
|
||||
@ -13949,12 +13508,6 @@
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/read-only-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
|
||||
@ -15123,27 +14676,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils/node_modules/escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/stackframe": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
|
||||
@ -15854,7 +15386,7 @@
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"devOptional": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||
"version": "2.0.1",
|
||||
|
||||
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"description": "Free and open source software program code available to run social networks.",
|
||||
"author": "ocelot.social Community",
|
||||
"license": "MIT",
|
||||
@ -36,18 +36,17 @@
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.1",
|
||||
"@babel/register": "^7.27.1",
|
||||
"@badeball/cypress-cucumber-preprocessor": "^22.1.0",
|
||||
"@badeball/cypress-cucumber-preprocessor": "^22.2.0",
|
||||
"@cucumber/cucumber": "11.3.0",
|
||||
"@cypress/browserify-preprocessor": "^3.0.2",
|
||||
"@faker-js/faker": "9.8.0",
|
||||
"@faker-js/faker": "9.9.0",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^14.5.0",
|
||||
"cypress": "^14.5.1",
|
||||
"cypress-network-idle": "^1.15.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"expect": "^29.6.4",
|
||||
"dotenv": "^17.2.0",
|
||||
"graphql-request": "^2.0.0",
|
||||
"import": "^0.0.6",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
|
||||
@ -56,6 +56,7 @@ describe('UserTeaser', () => {
|
||||
withLinkToProfile = true,
|
||||
onTouchScreen = false,
|
||||
withAvatar = true,
|
||||
showSlug = true,
|
||||
user = userTilda,
|
||||
withPopoverEnabled = true,
|
||||
}) => {
|
||||
@ -76,6 +77,7 @@ describe('UserTeaser', () => {
|
||||
user,
|
||||
linkToProfile: withLinkToProfile,
|
||||
showAvatar: withAvatar,
|
||||
showSlug: showSlug,
|
||||
showPopover: withPopoverEnabled,
|
||||
},
|
||||
stubs: {
|
||||
|
||||
@ -101,7 +101,7 @@ storiesOf('UserTeaser', module)
|
||||
data: () => ({
|
||||
user,
|
||||
}),
|
||||
template: '<user-teaser :user="user" />',
|
||||
template: '<user-teaser :user="user" :show-slug="true" />',
|
||||
}))
|
||||
.add('with date', () => ({
|
||||
components: { UserTeaser },
|
||||
@ -109,7 +109,7 @@ storiesOf('UserTeaser', module)
|
||||
data: () => ({
|
||||
user,
|
||||
}),
|
||||
template: '<user-teaser :user="user" :date-time="new Date()" />',
|
||||
template: '<user-teaser :user="user" :show-slug="true" :date-time="new Date()" />',
|
||||
}))
|
||||
.add('has edited something', () => ({
|
||||
components: { UserTeaser },
|
||||
@ -118,7 +118,7 @@ storiesOf('UserTeaser', module)
|
||||
user,
|
||||
}),
|
||||
template: `
|
||||
<user-teaser :user="user" :date-time="new Date()">
|
||||
<user-teaser :user="user" :show-slug="true" :date-time="new Date()">
|
||||
<template #dateTime>
|
||||
- HEY! I'm edited
|
||||
</template>
|
||||
@ -131,7 +131,7 @@ storiesOf('UserTeaser', module)
|
||||
data: () => ({
|
||||
user: null,
|
||||
}),
|
||||
template: '<user-teaser :user="user" :date-time="new Date()" />',
|
||||
template: '<user-teaser :user="user" :show-slug="true" :date-time="new Date()" />',
|
||||
}))
|
||||
.add('with group and date', () => ({
|
||||
components: { UserTeaser },
|
||||
@ -140,7 +140,8 @@ storiesOf('UserTeaser', module)
|
||||
user,
|
||||
group,
|
||||
}),
|
||||
template: '<user-teaser :user="user" :group="group" :date-time="new Date()" />',
|
||||
template:
|
||||
'<user-teaser :user="user" :show-slug="true" :group="group" :date-time="new Date()" />',
|
||||
}))
|
||||
.add('with group and date – wide', () => ({
|
||||
components: { UserTeaser },
|
||||
@ -149,5 +150,6 @@ storiesOf('UserTeaser', module)
|
||||
user,
|
||||
group,
|
||||
}),
|
||||
template: '<user-teaser :user="user" :group="group" wide :date-time="new Date()" />',
|
||||
template:
|
||||
'<user-teaser :user="user" :show-slug="true" :group="group" wide :date-time="new Date()" />',
|
||||
}))
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
:group="group"
|
||||
:wide="wide"
|
||||
:show-avatar="showAvatar"
|
||||
:show-slug="showSlug"
|
||||
:date-time="dateTime"
|
||||
:show-popover="showPopover"
|
||||
:injected-text="injectedText"
|
||||
@ -42,6 +43,7 @@ export default {
|
||||
group: { type: Object, default: null },
|
||||
wide: { type: Boolean, default: false },
|
||||
showAvatar: { type: Boolean, default: true },
|
||||
showSlug: { type: Boolean, default: false },
|
||||
dateTime: { type: [Date, String], default: null },
|
||||
showPopover: { type: Boolean, default: true },
|
||||
injectedText: { type: String, default: null },
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
@open-menu="loadPopover(openMenu)"
|
||||
@close-menu="closeMenu(false)"
|
||||
>
|
||||
<span class="slug">{{ userSlug }}</span>
|
||||
<span v-if="showSlug" class="slug">{{ userSlug }}</span>
|
||||
<span class="name">{{ userName }}</span>
|
||||
</user-teaser-helper>
|
||||
<span v-if="wide"> </span>
|
||||
@ -83,6 +83,7 @@ export default {
|
||||
group: { type: Object, default: null },
|
||||
wide: { type: Boolean, default: false },
|
||||
showAvatar: { type: Boolean, default: true },
|
||||
showSlug: { type: Boolean, default: false },
|
||||
dateTime: { type: [Date, String], default: null },
|
||||
showPopover: { type: Boolean, default: true },
|
||||
injectedText: { type: String, default: null },
|
||||
|
||||
@ -62,7 +62,7 @@ describe('FiledReportsTable.vue', () => {
|
||||
describe('FiledReport', () => {
|
||||
it('renders the reporting user', () => {
|
||||
const userSlug = wrapper.find('[data-test="filing-user"]')
|
||||
expect(userSlug.text()).toContain('@community-moderator')
|
||||
expect(userSlug.text()).toContain('Community moderator')
|
||||
})
|
||||
|
||||
it('renders the reported date', () => {
|
||||
|
||||
@ -109,7 +109,7 @@ describe('ReportRow', () => {
|
||||
|
||||
it('renders the moderator who reviewed the resource', () => {
|
||||
const username = wrapper.find('[data-test="report-reviewer"]')
|
||||
expect(username.text()).toContain('@moderator')
|
||||
expect(username.text()).toContain('Moderator')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -132,7 +132,7 @@ describe('ReportRow', () => {
|
||||
|
||||
it('renders the author', () => {
|
||||
const userSlug = wrapper.find('[data-test="report-author"]')
|
||||
expect(userSlug.text()).toContain('@louie')
|
||||
expect(userSlug.text()).toContain('Louie')
|
||||
})
|
||||
})
|
||||
|
||||
@ -154,7 +154,7 @@ describe('ReportRow', () => {
|
||||
|
||||
it('renders the author', () => {
|
||||
const username = wrapper.find('[data-test="report-author"]')
|
||||
expect(username.text()).toContain('@dagobert')
|
||||
expect(username.text()).toContain('Dagobert')
|
||||
})
|
||||
})
|
||||
|
||||
@ -171,7 +171,7 @@ describe('ReportRow', () => {
|
||||
|
||||
it('renders a link to the user profile', () => {
|
||||
const userLink = wrapper.find('[data-test="report-content"]')
|
||||
expect(userLink.text()).toContain('@abusive-user')
|
||||
expect(userLink.text()).toContain('Abusive user')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -87,8 +87,8 @@ describe('SearchableInput.vue', () => {
|
||||
it("pushes to user's profile", async () => {
|
||||
select.element.value = 'Bob'
|
||||
select.trigger('input')
|
||||
const users = wrapper.findAll('.slug')
|
||||
const bob = users.filter((item) => item.text().match(/@bob-der-baumeister/))
|
||||
const users = wrapper.findAll('.name')
|
||||
const bob = users.filter((item) => item.text().match(/Bob der Baumeister/))
|
||||
bob.trigger('click')
|
||||
await Vue.nextTick()
|
||||
expect(mocks.$router.push).toHaveBeenCalledWith({
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ocelot-social/maintenance",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"description": "Maintenance page for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ocelot-social-webapp",
|
||||
"version": "3.10.1",
|
||||
"version": "3.11.0",
|
||||
"description": "ocelot.social Frontend",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
@ -71,7 +71,7 @@
|
||||
"@babel/core": "^7.25.8",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.25.8",
|
||||
"@faker-js/faker": "9.8.0",
|
||||
"@faker-js/faker": "9.9.0",
|
||||
"@storybook/addon-a11y": "^8.0.8",
|
||||
"@storybook/addon-actions": "^5.3.21",
|
||||
"@storybook/addon-notes": "^5.3.18",
|
||||
|
||||
@ -561,11 +561,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -646,11 +642,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -731,11 +723,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -816,11 +804,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -2452,11 +2436,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -2537,11 +2517,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -2622,11 +2598,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -2707,11 +2679,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -3446,11 +3414,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -3531,11 +3495,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -3616,11 +3576,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -3701,11 +3657,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -4285,11 +4237,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -4370,11 +4318,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -4455,11 +4399,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -4540,11 +4480,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -5122,11 +5058,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -5207,11 +5139,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -5292,11 +5220,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -5377,11 +5301,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -6028,11 +5948,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -6113,11 +6029,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -6198,11 +6110,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -6283,11 +6191,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -7069,11 +6973,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -7154,11 +7054,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -7239,11 +7135,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -7324,11 +7216,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -8063,11 +7951,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@peter-lustig
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -8148,11 +8032,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@jenny-rostock
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -8233,11 +8113,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@bob-der-baumeister
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
@ -8318,11 +8194,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
<nuxt-link-stub
|
||||
to="[object Object]"
|
||||
>
|
||||
<span
|
||||
class="slug"
|
||||
>
|
||||
@huey
|
||||
</span>
|
||||
<!---->
|
||||
|
||||
<span
|
||||
class="name"
|
||||
|
||||
@ -2526,10 +2526,10 @@
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@faker-js/faker@9.8.0":
|
||||
version "9.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.8.0.tgz#3344284028d1c9dc98dee2479f82939310370d88"
|
||||
integrity sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==
|
||||
"@faker-js/faker@9.9.0":
|
||||
version "9.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.9.0.tgz#3ad015fbbaaae7af3149555e0f22b4b30134c69d"
|
||||
integrity sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==
|
||||
|
||||
"@human-connection/styleguide@0.5.22":
|
||||
version "0.5.22"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user