feat(server): simplified test running & IDE integrations
This commit is contained in:
Vendored
+7
-1
@@ -3,7 +3,13 @@
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "octref.vetur"],
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"octref.vetur",
|
||||
"hbenl.vscode-mocha-test-adapter",
|
||||
"ryanluker.vscode-coverage-gutters"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -13,6 +13,7 @@
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"search.useParentIgnoreFiles": true,
|
||||
"cSpell.enableFiletypes": ["vue-html", "vue-postcss"],
|
||||
"cSpell.userWords": ["Matomo", "SUPPRESSMSGBOXES", "matomo"],
|
||||
"cSpell.words": ["mixpanel"]
|
||||
|
||||
Generated
-56
@@ -10,7 +10,6 @@
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"lerna": "^3.22.1",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
@@ -5610,37 +5609,6 @@
|
||||
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/graphql": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz",
|
||||
"integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.16.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graphql-tag": {
|
||||
"version": "2.12.6",
|
||||
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
|
||||
"integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graphql-tag/node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.7",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
||||
@@ -15311,30 +15279,6 @@
|
||||
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
|
||||
"dev": true
|
||||
},
|
||||
"graphql": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz",
|
||||
"integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"graphql-tag": {
|
||||
"version": "2.12.6",
|
||||
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
|
||||
"integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.7.7",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"lerna": "^3.22.1"
|
||||
},
|
||||
"config": {
|
||||
|
||||
+4927
-4885
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,8 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^2.0.13",
|
||||
"eslint": "^8.11.0"
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,17 +47,17 @@
|
||||
"@vue/cli-plugin-vuex": "~4.3.0",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "~1.32.6",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue-cli-plugin-apollo": "~0.21.3",
|
||||
"vue-cli-plugin-vuetify": "^2.0.8",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuetify-loader": "^1.6.0"
|
||||
"vuetify-loader": "^1.6.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+42
@@ -10,6 +10,8 @@
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1",
|
||||
"undici": "^4.14.1"
|
||||
}
|
||||
},
|
||||
@@ -298,6 +300,18 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
|
||||
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
|
||||
@@ -729,6 +743,21 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@@ -1134,6 +1163,13 @@
|
||||
"v8-compile-cache": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
|
||||
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
|
||||
@@ -1468,6 +1504,12 @@
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"undici": "^4.14.1",
|
||||
"eslint": "^8.11.0"
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
+13546
-13550
File diff suppressed because it is too large
Load Diff
@@ -35,13 +35,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
"eslint": "^8.11.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"clean-webpack-plugin": "^4.0.0-alpha.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack-dev-server": "^4.6.0"
|
||||
"webpack-dev-server": "^4.6.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
###############################################
|
||||
# Env overrides when running app in test mode #
|
||||
###############################################
|
||||
|
||||
PORT=0
|
||||
POSTGRES_URL=postgres://speckle:speckle@localhost/speckle2_test
|
||||
POSTGRES_USER=''
|
||||
@@ -1,33 +1,12 @@
|
||||
'use strict'
|
||||
const fs = require( 'fs' )
|
||||
const path = require( 'path' )
|
||||
|
||||
function walk( dir ) {
|
||||
let results = [ ]
|
||||
let list = fs.readdirSync( dir )
|
||||
list.forEach( function ( file ) {
|
||||
let fullFile = path.join( dir, file )
|
||||
let stat = fs.statSync( fullFile )
|
||||
if ( stat && stat.isDirectory( ) ) {
|
||||
if ( file === 'tests' )
|
||||
results.push( fullFile )
|
||||
else
|
||||
results = results.concat( walk( fullFile ) )
|
||||
}
|
||||
} )
|
||||
return results
|
||||
/** @type {import("mocha").MochaOptions} */
|
||||
const config = {
|
||||
spec: ['modules/**/*.spec.js'],
|
||||
require: 'test/hooks.js',
|
||||
slow: 0,
|
||||
timeout: '10000',
|
||||
exit: true
|
||||
}
|
||||
|
||||
let testDirs = walk( './modules' )
|
||||
|
||||
|
||||
console.log( '\n' )
|
||||
console.log( `📝 Found ${testDirs.length} test dirs:` )
|
||||
console.log( testDirs )
|
||||
console.log( '\n' )
|
||||
|
||||
|
||||
module.exports = {
|
||||
spec: testDirs,
|
||||
require: 'test/hooks.js'
|
||||
}
|
||||
module.exports = config
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mochaExplorer.env": { "NODE_ENV": "test" }
|
||||
}
|
||||
+99
-82
@@ -1,29 +1,28 @@
|
||||
/* istanbul ignore file */
|
||||
'use strict'
|
||||
|
||||
const http = require( 'http' )
|
||||
const url = require( 'url' )
|
||||
const express = require( 'express' )
|
||||
const compression = require( 'compression' )
|
||||
const appRoot = require( 'app-root-path' )
|
||||
const logger = require( 'morgan-debug' )
|
||||
const bodyParser = require( 'body-parser' )
|
||||
const path = require( 'path' )
|
||||
const debug = require( 'debug' )
|
||||
const { createTerminus } = require( '@godaddy/terminus' )
|
||||
require('./bootstrap')
|
||||
const http = require('http')
|
||||
const url = require('url')
|
||||
const express = require('express')
|
||||
const compression = require('compression')
|
||||
const appRoot = require('app-root-path')
|
||||
const logger = require('morgan-debug')
|
||||
const bodyParser = require('body-parser')
|
||||
const path = require('path')
|
||||
const debug = require('debug')
|
||||
const { createTerminus } = require('@godaddy/terminus')
|
||||
|
||||
const Sentry = require( '@sentry/node' )
|
||||
const Tracing = require( '@sentry/tracing' )
|
||||
const Logging = require( `${appRoot}/logging` )
|
||||
const { startup: MatStartup } = require( `${appRoot}/logging/matomoHelper` )
|
||||
const prometheusClient = require( 'prom-client' )
|
||||
const Sentry = require('@sentry/node')
|
||||
const Tracing = require('@sentry/tracing')
|
||||
const Logging = require(`${appRoot}/logging`)
|
||||
const { startup: MatStartup } = require(`${appRoot}/logging/matomoHelper`)
|
||||
const prometheusClient = require('prom-client')
|
||||
|
||||
const { ApolloServer, ForbiddenError } = require( 'apollo-server-express' )
|
||||
const { ApolloServer, ForbiddenError } = require('apollo-server-express')
|
||||
|
||||
require( 'dotenv' ).config( { path: `${appRoot}/.env` } )
|
||||
|
||||
const { contextApiTokenHelper } = require( './modules/shared' )
|
||||
const knex = require( './db/knex' )
|
||||
const { contextApiTokenHelper } = require('./modules/shared')
|
||||
const knex = require('./db/knex')
|
||||
|
||||
let graphqlServer
|
||||
|
||||
@@ -31,10 +30,10 @@ let graphqlServer
|
||||
* Initialises the express application together with the graphql server middleware.
|
||||
* @return {[type]} an express application and the graphql server
|
||||
*/
|
||||
exports.init = async ( ) => {
|
||||
const app = express( )
|
||||
exports.init = async () => {
|
||||
const app = express()
|
||||
|
||||
Logging( app )
|
||||
Logging(app)
|
||||
MatStartup()
|
||||
|
||||
// Initialise prometheus metrics
|
||||
@@ -43,98 +42,112 @@ exports.init = async ( ) => {
|
||||
|
||||
// Moves things along automatically on restart.
|
||||
// Should perhaps be done manually?
|
||||
await knex.migrate.latest( )
|
||||
await knex.migrate.latest()
|
||||
|
||||
if ( process.env.NODE_ENV !== 'test' ) {
|
||||
app.use( logger( 'speckle', 'dev', {} ) )
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
app.use(logger('speckle', 'dev', {}))
|
||||
}
|
||||
|
||||
if ( process.env.COMPRESSION ) {
|
||||
app.use( compression( ) )
|
||||
if (process.env.COMPRESSION) {
|
||||
app.use(compression())
|
||||
}
|
||||
|
||||
app.use( bodyParser.json( { limit: '100mb' } ) )
|
||||
app.use( bodyParser.urlencoded( { limit: '100mb', extended: false } ) )
|
||||
app.use(bodyParser.json({ limit: '100mb' }))
|
||||
app.use(bodyParser.urlencoded({ limit: '100mb', extended: false }))
|
||||
|
||||
const { init, graph } = require( './modules' )
|
||||
const { init, graph } = require('./modules')
|
||||
|
||||
// Initialise default modules, including rest api handlers
|
||||
await init( app )
|
||||
await init(app)
|
||||
|
||||
// Initialise graphql server
|
||||
const metricConnectCounter = new prometheusClient.Counter( { name: 'speckle_server_apollo_connect', help: 'Number of connects' } )
|
||||
const metricConnectedClients = new prometheusClient.Gauge( { name: 'speckle_server_apollo_clients', help: 'Number of currently connected clients' } )
|
||||
graphqlServer = new ApolloServer( {
|
||||
...graph( ),
|
||||
const metricConnectCounter = new prometheusClient.Counter({
|
||||
name: 'speckle_server_apollo_connect',
|
||||
help: 'Number of connects'
|
||||
})
|
||||
const metricConnectedClients = new prometheusClient.Gauge({
|
||||
name: 'speckle_server_apollo_clients',
|
||||
help: 'Number of currently connected clients'
|
||||
})
|
||||
graphqlServer = new ApolloServer({
|
||||
...graph(),
|
||||
context: contextApiTokenHelper,
|
||||
subscriptions: {
|
||||
onConnect: ( connectionParams, webSocket, context ) => {
|
||||
onConnect: (connectionParams, webSocket, context) => {
|
||||
metricConnectCounter.inc()
|
||||
metricConnectedClients.inc()
|
||||
try {
|
||||
if ( connectionParams.Authorization || connectionParams.authorization || connectionParams.headers.Authorization ) {
|
||||
let header = connectionParams.Authorization || connectionParams.authorization || connectionParams.headers.Authorization
|
||||
let token = header.split( ' ' )[ 1 ]
|
||||
if (
|
||||
connectionParams.Authorization ||
|
||||
connectionParams.authorization ||
|
||||
connectionParams.headers.Authorization
|
||||
) {
|
||||
let header =
|
||||
connectionParams.Authorization ||
|
||||
connectionParams.authorization ||
|
||||
connectionParams.headers.Authorization
|
||||
let token = header.split(' ')[1]
|
||||
return { token: token }
|
||||
}
|
||||
} catch ( e ) {
|
||||
throw new ForbiddenError( 'You need a token to subscribe' )
|
||||
} catch (e) {
|
||||
throw new ForbiddenError('You need a token to subscribe')
|
||||
}
|
||||
},
|
||||
onDisconnect: ( webSocket, context ) => {
|
||||
onDisconnect: (webSocket, context) => {
|
||||
metricConnectedClients.dec()
|
||||
// debug( `speckle:debug` )( 'ws on disconnect connect event' )
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
require( `${appRoot}/logging/apolloPlugin` )
|
||||
],
|
||||
plugins: [require(`${appRoot}/logging/apolloPlugin`)],
|
||||
tracing: process.env.NODE_ENV === 'development',
|
||||
introspection: true,
|
||||
playground: true
|
||||
} )
|
||||
})
|
||||
|
||||
graphqlServer.applyMiddleware( { app: app } )
|
||||
graphqlServer.applyMiddleware({ app: app })
|
||||
|
||||
// Expose prometheus metrics
|
||||
app.get( '/metrics', async ( req, res ) => {
|
||||
app.get('/metrics', async (req, res) => {
|
||||
try {
|
||||
res.set( 'Content-Type', prometheusClient.register.contentType )
|
||||
res.end( await prometheusClient.register.metrics() )
|
||||
} catch ( ex ) {
|
||||
res.status( 500 ).end( ex.message )
|
||||
res.set('Content-Type', prometheusClient.register.contentType)
|
||||
res.end(await prometheusClient.register.metrics())
|
||||
} catch (ex) {
|
||||
res.status(500).end(ex.message)
|
||||
}
|
||||
} )
|
||||
})
|
||||
|
||||
// Trust X-Forwarded-* headers (for https protocol detection)
|
||||
app.enable( 'trust proxy' )
|
||||
app.enable('trust proxy')
|
||||
|
||||
return { app, graphqlServer }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts a http server, hoisting the express app to it.
|
||||
* @param {[type]} app [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
exports.startHttp = async ( app, customPortOverride ) => {
|
||||
exports.startHttp = async (app, customPortOverride) => {
|
||||
let bindAddress = process.env.BIND_ADDRESS || '127.0.0.1'
|
||||
let port = process.env.PORT || 3000
|
||||
|
||||
|
||||
let frontendHost = process.env.FRONTEND_HOST || 'localhost'
|
||||
let frontendPort = process.env.FRONTEND_PORT || 8080
|
||||
|
||||
// Handles frontend proxying:
|
||||
// Dev mode -> proxy form the local webpack server
|
||||
if ( process.env.NODE_ENV === 'development' ) {
|
||||
const { createProxyMiddleware } = require( 'http-proxy-middleware' )
|
||||
const frontendProxy = createProxyMiddleware( { target: `http://${frontendHost}:${frontendPort}`, changeOrigin: true, ws: false, logLevel: 'silent' } )
|
||||
app.use( '/', frontendProxy )
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware')
|
||||
const frontendProxy = createProxyMiddleware({
|
||||
target: `http://${frontendHost}:${frontendPort}`,
|
||||
changeOrigin: true,
|
||||
ws: false,
|
||||
logLevel: 'silent'
|
||||
})
|
||||
app.use('/', frontendProxy)
|
||||
|
||||
debug( 'speckle:startup' )( '✨ Proxying frontend (dev mode):' )
|
||||
debug( 'speckle:startup' )( `👉 main application: http://localhost:${port}/` )
|
||||
debug('speckle:startup')('✨ Proxying frontend (dev mode):')
|
||||
debug('speckle:startup')(`👉 main application: http://localhost:${port}/`)
|
||||
}
|
||||
|
||||
// Production mode
|
||||
@@ -142,39 +155,43 @@ exports.startHttp = async ( app, customPortOverride ) => {
|
||||
bindAddress = process.env.BIND_ADDRESS || '0.0.0.0'
|
||||
}
|
||||
|
||||
let server = http.createServer( app )
|
||||
let server = http.createServer(app)
|
||||
|
||||
if ( customPortOverride || customPortOverride === 0 ) port = customPortOverride
|
||||
app.set( 'port', port )
|
||||
if (customPortOverride || customPortOverride === 0) port = customPortOverride
|
||||
app.set('port', port)
|
||||
|
||||
// Final apollo server setup
|
||||
graphqlServer.installSubscriptionHandlers( server )
|
||||
graphqlServer.applyMiddleware( { app: app } )
|
||||
graphqlServer.installSubscriptionHandlers(server)
|
||||
graphqlServer.applyMiddleware({ app: app })
|
||||
|
||||
app.use( Sentry.Handlers.errorHandler( ) )
|
||||
app.use(Sentry.Handlers.errorHandler())
|
||||
|
||||
// large timeout to allow large downloads on slow connections to finish
|
||||
createTerminus( server, {
|
||||
signals: [ 'SIGTERM', 'SIGINT' ],
|
||||
createTerminus(server, {
|
||||
signals: ['SIGTERM', 'SIGINT'],
|
||||
timeout: 5 * 60 * 1000,
|
||||
beforeShutdown: () => {
|
||||
debug( 'speckle:shutdown' )( 'Shutting down (signal received)...' )
|
||||
debug('speckle:shutdown')('Shutting down (signal received)...')
|
||||
},
|
||||
onSignal: () => {
|
||||
// Other custom cleanup after connections are finished
|
||||
},
|
||||
onShutdown: () => {
|
||||
debug( 'speckle:shutdown' )( 'Shutdown completed' )
|
||||
process.exit( 0 )
|
||||
debug('speckle:shutdown')('Shutdown completed')
|
||||
process.exit(0)
|
||||
}
|
||||
} )
|
||||
})
|
||||
|
||||
server.on( 'listening', ( ) => {
|
||||
debug( 'speckle:startup' )( `🚀 My name is Speckle Server, and I'm running at ${server.address().address}:${server.address().port}` )
|
||||
app.emit( 'appStarted' )
|
||||
} )
|
||||
server.on('listening', () => {
|
||||
debug('speckle:startup')(
|
||||
`🚀 My name is Speckle Server, and I'm running at ${server.address().address}:${
|
||||
server.address().port
|
||||
}`
|
||||
)
|
||||
app.emit('appStarted')
|
||||
})
|
||||
|
||||
server.listen( port, bindAddress )
|
||||
server.listen(port, bindAddress)
|
||||
|
||||
server.keepAliveTimeout = 61 * 1000
|
||||
server.headersTimeout = 65 * 1000
|
||||
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Bootstrap module that should be imported at the very top of each entry point module
|
||||
*/
|
||||
const appRoot = require('app-root-path')
|
||||
const dotenv = require('dotenv')
|
||||
const { isTestEnv } = require('./modules/core/helpers/envHelper')
|
||||
|
||||
// If running in test env, load .env.test first
|
||||
if (isTestEnv()) {
|
||||
dotenv.config({ path: `${appRoot}/.env.test` })
|
||||
}
|
||||
|
||||
dotenv.config({ path: `${appRoot}/.env` })
|
||||
@@ -2,16 +2,20 @@
|
||||
'use strict'
|
||||
|
||||
let env = process.env.NODE_ENV || 'development'
|
||||
let conf = require( '../knexfile.js' )[ env ]
|
||||
let conf = require('../knexfile.js')[env]
|
||||
|
||||
conf.log = {
|
||||
warn( message ) {
|
||||
if ( message === 'FS-related option specified for migration configuration. This resets migrationSource to default FsMigrations' ) return
|
||||
warn(message) {
|
||||
if (
|
||||
message ===
|
||||
'FS-related option specified for migration configuration. This resets migrationSource to default FsMigrations'
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const debug = require( 'debug' )
|
||||
const debug = require('debug')
|
||||
|
||||
debug( 'speckle:db-startup' )( `Loaded knex conf for ${env}` )
|
||||
debug('speckle:db-startup')(`Loaded knex conf for ${env}`)
|
||||
|
||||
module.exports = require( 'knex' )( conf )
|
||||
module.exports = require('knex')(conf)
|
||||
|
||||
+20
-23
@@ -1,37 +1,34 @@
|
||||
/* istanbul ignore file */
|
||||
'use strict'
|
||||
const fs = require( 'fs' )
|
||||
const path = require( 'path' )
|
||||
const appRoot = require( 'app-root-path' )
|
||||
|
||||
// Load up .ENV file
|
||||
require('./bootstrap')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
require( 'dotenv' ).config( { path: `${appRoot}/.env` } )
|
||||
|
||||
function walk( dir ) {
|
||||
let results = [ ]
|
||||
let list = fs.readdirSync( dir )
|
||||
list.forEach( function ( file ) {
|
||||
let fullFile = path.join( dir, file )
|
||||
let stat = fs.statSync( fullFile )
|
||||
if ( stat && stat.isDirectory( ) ) {
|
||||
if ( file === 'migrations' )
|
||||
results.push( fullFile )
|
||||
else
|
||||
results = results.concat( walk( fullFile ) )
|
||||
function walk(dir) {
|
||||
let results = []
|
||||
let list = fs.readdirSync(dir)
|
||||
list.forEach(function (file) {
|
||||
let fullFile = path.join(dir, file)
|
||||
let stat = fs.statSync(fullFile)
|
||||
if (stat && stat.isDirectory()) {
|
||||
if (file === 'migrations') results.push(fullFile)
|
||||
else results = results.concat(walk(fullFile))
|
||||
}
|
||||
} )
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
let migrationDirs = walk( './modules' )
|
||||
let migrationDirs = walk('./modules')
|
||||
|
||||
// this is for readability, many users struggle to set the postgres connection uri
|
||||
// in the env variables. This way its a bit easier to understand, also backward compatible.
|
||||
let env = process.env
|
||||
let connectionUri
|
||||
if ( env.POSTGRES_USER && env.POSTGRES_PASSWORD ) {
|
||||
connectionUri = `postgres://${encodeURIComponent( env.POSTGRES_USER )}:${encodeURIComponent( env.POSTGRES_PASSWORD )}@${env.POSTGRES_URL}/${encodeURIComponent( env.POSTGRES_DB )}`
|
||||
if (env.POSTGRES_USER && env.POSTGRES_PASSWORD) {
|
||||
connectionUri = `postgres://${encodeURIComponent(env.POSTGRES_USER)}:${encodeURIComponent(
|
||||
env.POSTGRES_PASSWORD
|
||||
)}@${env.POSTGRES_URL}/${encodeURIComponent(env.POSTGRES_DB)}`
|
||||
} else {
|
||||
connectionUri = env.POSTGRES_URL
|
||||
}
|
||||
@@ -42,14 +39,14 @@ module.exports = {
|
||||
connection: connectionUri || 'postgres://localhost/speckle2_test',
|
||||
migrations: {
|
||||
directory: migrationDirs
|
||||
},
|
||||
}
|
||||
},
|
||||
development: {
|
||||
client: 'pg',
|
||||
connection: connectionUri || 'postgres://localhost/speckle2_dev',
|
||||
migrations: {
|
||||
directory: migrationDirs
|
||||
},
|
||||
}
|
||||
},
|
||||
production: {
|
||||
client: 'pg',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
function isTestEnv() {
|
||||
return process.env.NODE_ENV === 'test'
|
||||
}
|
||||
|
||||
function isDevEnv() {
|
||||
return process.env.NODE_ENV === 'development'
|
||||
}
|
||||
|
||||
function isProdEnv() {
|
||||
return process.env.NODE_ENV === 'production'
|
||||
}
|
||||
|
||||
module.exports = { isTestEnv, isDevEnv, isProdEnv }
|
||||
@@ -1,42 +1,41 @@
|
||||
const appRoot = require( 'app-root-path' )
|
||||
require( 'dotenv' ).config( { 'path': `${appRoot}/.env` } )
|
||||
const crs = require( 'crypto-random-string' )
|
||||
require('../../../bootstrap')
|
||||
|
||||
const knex = require( `${appRoot}/db/knex` )
|
||||
const Verifications = () => knex( 'email_verifications' )
|
||||
const appRoot = require('app-root-path')
|
||||
const crs = require('crypto-random-string')
|
||||
|
||||
const { getServerInfo } = require( `${appRoot}/modules/core/services/generic` )
|
||||
const { sendEmail } = require( `${appRoot}/modules/emails` )
|
||||
const knex = require(`${appRoot}/db/knex`)
|
||||
const Verifications = () => knex('email_verifications')
|
||||
|
||||
const sendEmailVerification = async ( { recipient } ) => {
|
||||
const { getServerInfo } = require(`${appRoot}/modules/core/services/generic`)
|
||||
const { sendEmail } = require(`${appRoot}/modules/emails`)
|
||||
|
||||
const sendEmailVerification = async ({ recipient }) => {
|
||||
// we need to validate email here, since we'll send it out,
|
||||
// even if technically there is no chance ATM that an incorrect addr comes in
|
||||
const serverInfo = await getServerInfo()
|
||||
const existingVerifications = await Verifications()
|
||||
.where( { 'email': recipient } )
|
||||
if ( existingVerifications.some( ver => isVerificationValid( ver ) ) )
|
||||
throw new Error( 'You already have a valid verification message, please check your inbox' )
|
||||
const verificationId = await createEmailVerification( { 'email': recipient } )
|
||||
const existingVerifications = await Verifications().where({ email: recipient })
|
||||
if (existingVerifications.some((ver) => isVerificationValid(ver)))
|
||||
throw new Error('You already have a valid verification message, please check your inbox')
|
||||
const verificationId = await createEmailVerification({ email: recipient })
|
||||
const verificationLink = new URL(
|
||||
`auth/verifyemail?t=${verificationId}`, process.env.CANONICAL_URL,
|
||||
`auth/verifyemail?t=${verificationId}`,
|
||||
process.env.CANONICAL_URL
|
||||
)
|
||||
const { text, html, subject } = await prepareMessage(
|
||||
{ verificationLink, serverInfo },
|
||||
)
|
||||
return await sendEmail( {
|
||||
'to': recipient,
|
||||
const { text, html, subject } = await prepareMessage({ verificationLink, serverInfo })
|
||||
return await sendEmail({
|
||||
to: recipient,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
} )
|
||||
html
|
||||
})
|
||||
}
|
||||
|
||||
const isVerificationValid = ( { createdAt } ) => {
|
||||
const timeDiff = Math.abs( Date.now() - new Date( createdAt ) )
|
||||
return timeDiff < 8.64e+7
|
||||
const isVerificationValid = ({ createdAt }) => {
|
||||
const timeDiff = Math.abs(Date.now() - new Date(createdAt))
|
||||
return timeDiff < 8.64e7
|
||||
}
|
||||
|
||||
const prepareMessage = async ( { verificationLink, serverInfo } ) => {
|
||||
const prepareMessage = async ({ verificationLink, serverInfo }) => {
|
||||
const subject = `Speckle Server ${serverInfo.name} email verification`
|
||||
const text = `
|
||||
Hello!
|
||||
@@ -90,12 +89,12 @@ This email was sent from the
|
||||
return { text, html, subject }
|
||||
}
|
||||
|
||||
const createEmailVerification = async ( { email } ) => {
|
||||
const createEmailVerification = async ({ email }) => {
|
||||
const verification = {
|
||||
'id': crs( { 'length': 20 } ),
|
||||
email,
|
||||
id: crs({ length: 20 }),
|
||||
email
|
||||
}
|
||||
await Verifications().insert( verification )
|
||||
await Verifications().insert(verification)
|
||||
return verification.id
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +1,97 @@
|
||||
/* istanbul ignore file */
|
||||
const expect = require( 'chai' ).expect
|
||||
const expect = require('chai').expect
|
||||
|
||||
const appRoot = require( 'app-root-path' )
|
||||
const appRoot = require('app-root-path')
|
||||
|
||||
const { createUser } = require( `${appRoot}/modules/core/services/users` )
|
||||
const { createPersonalAccessToken } = require( `${appRoot}/modules/core/services/tokens` )
|
||||
const { createStream } = require( `${appRoot}/modules/core/services/streams` )
|
||||
const { createObjects } = require( `${appRoot}/modules/core/services/objects` )
|
||||
const { createCommitByBranchName } = require( `${appRoot}/modules/core/services/commits` )
|
||||
const { createUser } = require(`${appRoot}/modules/core/services/users`)
|
||||
const { createPersonalAccessToken } = require(`${appRoot}/modules/core/services/tokens`)
|
||||
const { createStream } = require(`${appRoot}/modules/core/services/streams`)
|
||||
const { createObjects } = require(`${appRoot}/modules/core/services/objects`)
|
||||
const { createCommitByBranchName } = require(`${appRoot}/modules/core/services/commits`)
|
||||
|
||||
const { beforeEachContext, initializeTestServer } = require( `${appRoot}/test/hooks` )
|
||||
const { createManyObjects } = require( `${appRoot}/test/helpers` )
|
||||
const { beforeEachContext, initializeTestServer } = require(`${appRoot}/test/hooks`)
|
||||
const { createManyObjects } = require(`${appRoot}/test/helpers`)
|
||||
|
||||
const { getStreamHistory, getCommitHistory, getObjectHistory, getUserHistory, getTotalStreamCount, getTotalCommitCount, getTotalObjectCount, getTotalUserCount } = require( '../services' )
|
||||
const {
|
||||
getStreamHistory,
|
||||
getCommitHistory,
|
||||
getObjectHistory,
|
||||
getUserHistory,
|
||||
getTotalStreamCount,
|
||||
getTotalCommitCount,
|
||||
getTotalObjectCount,
|
||||
getTotalUserCount
|
||||
} = require('../services')
|
||||
|
||||
const params = { numUsers: 25, numStreams: 30, numObjects: 100, numCommits: 100 }
|
||||
|
||||
describe( 'Server stats services @stats-services', function() {
|
||||
before( async function() {
|
||||
this.timeout( 10000 )
|
||||
describe('Server stats services @stats-services', function () {
|
||||
before(async function () {
|
||||
this.timeout(10000)
|
||||
|
||||
await beforeEachContext( )
|
||||
await seedDb( params )
|
||||
} )
|
||||
await beforeEachContext()
|
||||
await seedDb(params)
|
||||
})
|
||||
|
||||
it( 'should return the total number of users on this server', async () => {
|
||||
it('should return the total number of users on this server', async () => {
|
||||
let res = await getTotalUserCount()
|
||||
expect( res ).to.equal( params.numUsers )
|
||||
} )
|
||||
expect(res).to.equal(params.numUsers)
|
||||
})
|
||||
|
||||
it( 'should return the total number of streams on this server', async() => {
|
||||
it('should return the total number of streams on this server', async () => {
|
||||
let res = await getTotalStreamCount()
|
||||
expect( res ).to.equal( params.numStreams )
|
||||
} )
|
||||
expect(res).to.equal(params.numStreams)
|
||||
})
|
||||
|
||||
it( 'should return the total number of commits on this server', async() => {
|
||||
it('should return the total number of commits on this server', async () => {
|
||||
let res = await getTotalCommitCount()
|
||||
expect( res ).to.equal( params.numCommits )
|
||||
} )
|
||||
expect(res).to.equal(params.numCommits)
|
||||
})
|
||||
|
||||
it( 'should return the total number of objects on this server', async() => {
|
||||
it('should return the total number of objects on this server', async () => {
|
||||
let res = await getTotalObjectCount()
|
||||
expect( res ).to.equal( params.numObjects )
|
||||
} )
|
||||
expect(res).to.equal(params.numObjects)
|
||||
})
|
||||
|
||||
it( 'should return the stream creation history by month', async() => {
|
||||
it('should return the stream creation history by month', async () => {
|
||||
let res = await getStreamHistory()
|
||||
expect( res ).to.be.an( 'array' )
|
||||
expect( res[0] ).to.have.property( 'count' )
|
||||
expect( res[0] ).to.have.property( 'created_month' )
|
||||
expect( res[0].count ).to.be.a( 'number' )
|
||||
expect( res[0].count ).to.equal( params.numStreams )
|
||||
} )
|
||||
expect(res).to.be.an('array')
|
||||
expect(res[0]).to.have.property('count')
|
||||
expect(res[0]).to.have.property('created_month')
|
||||
expect(res[0].count).to.be.a('number')
|
||||
expect(res[0].count).to.equal(params.numStreams)
|
||||
})
|
||||
|
||||
it( 'should return the commit creation history by month', async() => {
|
||||
it('should return the commit creation history by month', async () => {
|
||||
let res = await getCommitHistory()
|
||||
expect( res ).to.be.an( 'array' )
|
||||
expect( res[0] ).to.have.property( 'count' )
|
||||
expect( res[0] ).to.have.property( 'created_month' )
|
||||
expect( res[0].count ).to.be.a( 'number' )
|
||||
expect( res[0].count ).to.equal( params.numCommits )
|
||||
} )
|
||||
expect(res).to.be.an('array')
|
||||
expect(res[0]).to.have.property('count')
|
||||
expect(res[0]).to.have.property('created_month')
|
||||
expect(res[0].count).to.be.a('number')
|
||||
expect(res[0].count).to.equal(params.numCommits)
|
||||
})
|
||||
|
||||
it( 'should return the object creation history by month', async() => {
|
||||
it('should return the object creation history by month', async () => {
|
||||
let res = await getObjectHistory()
|
||||
expect( res ).to.be.an( 'array' )
|
||||
expect( res[0] ).to.have.property( 'count' )
|
||||
expect( res[0] ).to.have.property( 'created_month' )
|
||||
expect( res[0].count ).to.be.a( 'number' )
|
||||
expect( res[0].count ).to.equal( params.numObjects )
|
||||
} )
|
||||
expect(res).to.be.an('array')
|
||||
expect(res[0]).to.have.property('count')
|
||||
expect(res[0]).to.have.property('created_month')
|
||||
expect(res[0].count).to.be.a('number')
|
||||
expect(res[0].count).to.equal(params.numObjects)
|
||||
})
|
||||
|
||||
it( 'should return the user creation history by month', async() => {
|
||||
it('should return the user creation history by month', async () => {
|
||||
let res = await getUserHistory()
|
||||
expect( res ).to.be.an( 'array' )
|
||||
expect( res[0] ).to.have.property( 'count' )
|
||||
expect( res[0] ).to.have.property( 'created_month' )
|
||||
expect( res[0].count ).to.be.a( 'number' )
|
||||
expect( res[0].count ).to.equal( params.numUsers )
|
||||
} )
|
||||
} )
|
||||
expect(res).to.be.an('array')
|
||||
expect(res[0]).to.have.property('count')
|
||||
expect(res[0]).to.have.property('created_month')
|
||||
expect(res[0].count).to.be.a('number')
|
||||
expect(res[0].count).to.equal(params.numUsers)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe( 'Server stats api @stats-api', function() {
|
||||
let server
|
||||
describe('Server stats api @stats-api', function () {
|
||||
let server, sendRequest
|
||||
|
||||
let adminUser = {
|
||||
name: 'Dimitrie',
|
||||
@@ -112,100 +120,123 @@ describe( 'Server stats api @stats-api', function() {
|
||||
}
|
||||
`
|
||||
|
||||
before( async function() {
|
||||
this.timeout( 10000 )
|
||||
before(async function () {
|
||||
this.timeout(10000)
|
||||
|
||||
let { app } = await beforeEachContext( );
|
||||
( { server, sendRequest } = await initializeTestServer( app ) )
|
||||
let { app } = await beforeEachContext()
|
||||
;({ server, sendRequest } = await initializeTestServer(app))
|
||||
|
||||
adminUser.id = await createUser( adminUser )
|
||||
adminUser.goodToken = `Bearer ${( await createPersonalAccessToken( adminUser.id, 'test token user A', [ 'server:stats' ] ) )}`
|
||||
adminUser.badToken = `Bearer ${( await createPersonalAccessToken( adminUser.id, 'test token user A', [ 'streams:read' ] ) )}`
|
||||
adminUser.id = await createUser(adminUser)
|
||||
adminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
||||
adminUser.id,
|
||||
'test token user A',
|
||||
['server:stats']
|
||||
)}`
|
||||
adminUser.badToken = `Bearer ${await createPersonalAccessToken(
|
||||
adminUser.id,
|
||||
'test token user A',
|
||||
['streams:read']
|
||||
)}`
|
||||
|
||||
notAdminUser.id = await createUser( notAdminUser )
|
||||
notAdminUser.goodToken = `Bearer ${( await createPersonalAccessToken( notAdminUser.id, 'test token user A', [ 'server:stats' ] ) )}`
|
||||
notAdminUser.badToken = `Bearer ${( await createPersonalAccessToken( notAdminUser.id, 'test token user A', [ 'streams:read' ] ) )}`
|
||||
notAdminUser.id = await createUser(notAdminUser)
|
||||
notAdminUser.goodToken = `Bearer ${await createPersonalAccessToken(
|
||||
notAdminUser.id,
|
||||
'test token user A',
|
||||
['server:stats']
|
||||
)}`
|
||||
notAdminUser.badToken = `Bearer ${await createPersonalAccessToken(
|
||||
notAdminUser.id,
|
||||
'test token user A',
|
||||
['streams:read']
|
||||
)}`
|
||||
|
||||
await seedDb( params )
|
||||
} )
|
||||
await seedDb(params)
|
||||
})
|
||||
|
||||
after( async function() {
|
||||
await server.close( )
|
||||
} )
|
||||
after(async function () {
|
||||
await server.close()
|
||||
})
|
||||
|
||||
it( 'Should not get stats if user is not admin', async() => {
|
||||
let res = await sendRequest( adminUser.badToken, { query: fullQuery } )
|
||||
expect( res.body.errors ).to.exist
|
||||
expect( res.body.errors[0].extensions.code ).to.equal( 'FORBIDDEN' )
|
||||
} )
|
||||
it('Should not get stats if user is not admin', async () => {
|
||||
let res = await sendRequest(adminUser.badToken, { query: fullQuery })
|
||||
expect(res.body.errors).to.exist
|
||||
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
|
||||
})
|
||||
|
||||
it( 'Should not get stats if user is not admin even if the token has the correct scopes', async() => {
|
||||
let res = await sendRequest( notAdminUser.goodToken, { query: fullQuery } )
|
||||
expect( res.body.errors ).to.exist
|
||||
expect( res.body.errors[0].extensions.code ).to.equal( 'FORBIDDEN' )
|
||||
} )
|
||||
it('Should not get stats if user is not admin even if the token has the correct scopes', async () => {
|
||||
let res = await sendRequest(notAdminUser.goodToken, { query: fullQuery })
|
||||
expect(res.body.errors).to.exist
|
||||
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
|
||||
})
|
||||
|
||||
it( 'Should not get stats if token does not have required scope', async() => {
|
||||
let res = await sendRequest( adminUser.badToken, { query: fullQuery } )
|
||||
expect( res ).to.be.json
|
||||
expect( res.body.errors ).to.exist
|
||||
expect( res.body.errors[0].extensions.code ).to.equal( 'FORBIDDEN' )
|
||||
} )
|
||||
it('Should not get stats if token does not have required scope', async () => {
|
||||
let res = await sendRequest(adminUser.badToken, { query: fullQuery })
|
||||
expect(res).to.be.json
|
||||
expect(res.body.errors).to.exist
|
||||
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
|
||||
})
|
||||
|
||||
it( 'Should get server stats', async() => {
|
||||
let res = await sendRequest( adminUser.goodToken, { query: fullQuery } )
|
||||
expect( res ).to.be.json
|
||||
expect( res.body.errors ).to.not.exist
|
||||
it('Should get server stats', async () => {
|
||||
let res = await sendRequest(adminUser.goodToken, { query: fullQuery })
|
||||
expect(res).to.be.json
|
||||
expect(res.body.errors).to.not.exist
|
||||
|
||||
expect( res.body.data ).to.have.property( 'serverStats' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'totalStreamCount' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'totalCommitCount' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'totalObjectCount' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'totalUserCount' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'streamHistory' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'commitHistory' )
|
||||
expect( res.body.data.serverStats ).to.have.property( 'userHistory' )
|
||||
expect(res.body.data).to.have.property('serverStats')
|
||||
expect(res.body.data.serverStats).to.have.property('totalStreamCount')
|
||||
expect(res.body.data.serverStats).to.have.property('totalCommitCount')
|
||||
expect(res.body.data.serverStats).to.have.property('totalObjectCount')
|
||||
expect(res.body.data.serverStats).to.have.property('totalUserCount')
|
||||
expect(res.body.data.serverStats).to.have.property('streamHistory')
|
||||
expect(res.body.data.serverStats).to.have.property('commitHistory')
|
||||
expect(res.body.data.serverStats).to.have.property('userHistory')
|
||||
|
||||
expect( res.body.data.serverStats.totalStreamCount ).to.equal( params.numStreams )
|
||||
expect( res.body.data.serverStats.totalCommitCount ).to.equal( params.numCommits )
|
||||
expect( res.body.data.serverStats.totalObjectCount ).to.equal( params.numObjects )
|
||||
expect( res.body.data.serverStats.totalUserCount ).to.equal( params.numUsers + 2 ) // we're registering two extra users in the before hook
|
||||
expect(res.body.data.serverStats.totalStreamCount).to.equal(params.numStreams)
|
||||
expect(res.body.data.serverStats.totalCommitCount).to.equal(params.numCommits)
|
||||
expect(res.body.data.serverStats.totalObjectCount).to.equal(params.numObjects)
|
||||
expect(res.body.data.serverStats.totalUserCount).to.equal(params.numUsers + 2) // we're registering two extra users in the before hook
|
||||
|
||||
expect( res.body.data.serverStats.streamHistory ).to.be.an( 'array' )
|
||||
expect( res.body.data.serverStats.commitHistory ).to.be.an( 'array' )
|
||||
expect( res.body.data.serverStats.userHistory ).to.be.an( 'array' )
|
||||
} )
|
||||
} )
|
||||
expect(res.body.data.serverStats.streamHistory).to.be.an('array')
|
||||
expect(res.body.data.serverStats.commitHistory).to.be.an('array')
|
||||
expect(res.body.data.serverStats.userHistory).to.be.an('array')
|
||||
})
|
||||
})
|
||||
|
||||
async function seedDb( { numUsers = 10, numStreams = 10, numObjects = 10, numCommits = 10 } = {} ) {
|
||||
async function seedDb({ numUsers = 10, numStreams = 10, numObjects = 10, numCommits = 10 } = {}) {
|
||||
let users = []
|
||||
let streams = []
|
||||
|
||||
// create users
|
||||
for ( let i = 0; i < numUsers; i++ ) {
|
||||
let id = await createUser( { name: `User ${i}`, password: `SuperSecure${i}${i*3.14}`, email: `user${i}@speckle.systems` } )
|
||||
users.push( id )
|
||||
for (let i = 0; i < numUsers; i++) {
|
||||
let id = await createUser({
|
||||
name: `User ${i}`,
|
||||
password: `SuperSecure${i}${i * 3.14}`,
|
||||
email: `user${i}@speckle.systems`
|
||||
})
|
||||
users.push(id)
|
||||
}
|
||||
|
||||
// create streams
|
||||
for ( let i = 0; i < numStreams; i++ ) {
|
||||
let id = await createStream( { name: `Stream ${i}`, ownerId: users[i >= users.length ? users.length - 1 : i ] } )
|
||||
streams.push( id )
|
||||
for (let i = 0; i < numStreams; i++) {
|
||||
let id = await createStream({
|
||||
name: `Stream ${i}`,
|
||||
ownerId: users[i >= users.length ? users.length - 1 : i]
|
||||
})
|
||||
streams.push(id)
|
||||
}
|
||||
|
||||
// create a objects
|
||||
let mockObjects = createManyObjects( numObjects - 1 )
|
||||
let objs = await createObjects( streams[ 0 ], mockObjects )
|
||||
let mockObjects = createManyObjects(numObjects - 1)
|
||||
let objs = await createObjects(streams[0], mockObjects)
|
||||
let commits = []
|
||||
|
||||
// create commits referencing those objects
|
||||
for ( let i = 0; i < numCommits; i++ ) {
|
||||
let id = await createCommitByBranchName( {
|
||||
for (let i = 0; i < numCommits; i++) {
|
||||
let id = await createCommitByBranchName({
|
||||
streamId: streams[0],
|
||||
branchName: 'main',
|
||||
sourceApplication: 'tests',
|
||||
objectId: objs[ i >= objs.length ? objs.length - 1 : i ]
|
||||
} )
|
||||
commits.push( id )
|
||||
objectId: objs[i >= objs.length ? objs.length - 1 : i]
|
||||
})
|
||||
commits.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+40795
-40740
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development DEBUG=speckle:* nodemon ./bin/www --watch . --watch ./bin/www -e js,graphql,env",
|
||||
"dev:server:test": "cross-env NODE_ENV=test DEBUG=speckle:* node ./bin/www",
|
||||
"test": "cross-env PORT=0 NODE_ENV=test POSTGRES_URL=postgres://speckle:speckle@localhost/speckle2_test POSTGRES_USER='' nyc --reporter html --reporter lcov mocha -s 0 --timeout 10000 --exit",
|
||||
"test:report": "npm run test -- --reporter mocha-junit-reporter --reporter-options mochaFile=reports/test-results.xml",
|
||||
"test": "cross-env NODE_ENV=test mocha",
|
||||
"test:coverage": "cross-env NODE_ENV=test nyc --reporter lcov mocha",
|
||||
"lint": "eslint . --ext .js,.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -82,15 +82,17 @@
|
||||
"chai-http": "^4.3.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.11.0",
|
||||
"http-proxy-middleware": "^1.0.6",
|
||||
"lerna": "^3.22.1",
|
||||
"mocha": "^7.2.0",
|
||||
"mocha-junit-reporter": "^2.0.2",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"nodemon": "^2.0.6",
|
||||
"nyc": "^15.0.1",
|
||||
"supertest": "^4.0.2",
|
||||
"ws": "^7.5.7"
|
||||
"ws": "^7.5.7",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
||||
Generated
+42
@@ -32,9 +32,11 @@
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"jest": "27.2.5",
|
||||
"mocha": "^9.1.2",
|
||||
"prettier": "^2.5.1",
|
||||
"webpack": "5.58.2",
|
||||
"webpack-cli": "^4.9.0",
|
||||
"webpack-dev-server": "^4.3.1",
|
||||
@@ -5213,6 +5215,18 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
|
||||
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -9928,6 +9942,21 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-error": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
|
||||
@@ -16242,6 +16271,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
|
||||
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
@@ -19645,6 +19681,12 @@
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-error": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
"3d viewer",
|
||||
"threejs"
|
||||
],
|
||||
"dependencies": {
|
||||
"@speckle/objectloader": "^2.3.0",
|
||||
"camera-controls": "^1.33.1",
|
||||
"hold-event": "^0.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"rainbowvis.js": "^1.0.1",
|
||||
"three": "^0.136.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.15.7",
|
||||
"@babel/core": "7.15.8",
|
||||
@@ -45,21 +53,15 @@
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "^8.11.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"jest": "27.2.5",
|
||||
"mocha": "^9.1.2",
|
||||
"webpack": "5.58.2",
|
||||
"webpack-cli": "^4.9.0",
|
||||
"webpack-dev-server": "^4.3.1",
|
||||
"yargs": "^17.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@speckle/objectloader": "^2.3.0",
|
||||
"camera-controls": "^1.33.1",
|
||||
"hold-event": "^0.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"rainbowvis.js": "^1.0.1",
|
||||
"three": "^0.136.0"
|
||||
"yargs": "^17.2.1",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
+43
-1
@@ -15,7 +15,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.11.0"
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
@@ -349,6 +351,18 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
|
||||
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
|
||||
@@ -1063,6 +1077,21 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/private-ip": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/private-ip/-/private-ip-2.3.3.tgz",
|
||||
@@ -1586,6 +1615,13 @@
|
||||
"v8-compile-cache": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
|
||||
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
|
||||
@@ -2107,6 +2143,12 @@
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||
"dev": true
|
||||
},
|
||||
"private-ip": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/private-ip/-/private-ip-2.3.3.tgz",
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.11.0"
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,21 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"search.useParentIgnoreFiles": true
|
||||
},
|
||||
"extensions": {
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "octref.vetur"],
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"octref.vetur",
|
||||
"hbenl.vscode-mocha-test-adapter",
|
||||
"ryanluker.vscode-coverage-gutters"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user