mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
Merge branch 'main' into versions/3.0.0
# Conflicts: # package-lock.json # test/integration/Identity.test.ts # test/integration/RepresentationConverter.test.ts
This commit is contained in:
commit
90a6460c8d
@ -83,6 +83,10 @@ module.exports = {
|
|||||||
'unicorn/no-fn-reference-in-iterator': 'off',
|
'unicorn/no-fn-reference-in-iterator': 'off',
|
||||||
'unicorn/no-object-as-default-parameter': 'off',
|
'unicorn/no-object-as-default-parameter': 'off',
|
||||||
'unicorn/numeric-separators-style': 'off',
|
'unicorn/numeric-separators-style': 'off',
|
||||||
|
// At function only supported in Node v16.6.0
|
||||||
|
'unicorn/prefer-at': 'off',
|
||||||
|
// Does not make sense for more complex cases
|
||||||
|
'unicorn/prefer-object-from-entries': 'off',
|
||||||
// Can get ugly with large single statements
|
// Can get ugly with large single statements
|
||||||
'unicorn/prefer-ternary': 'off',
|
'unicorn/prefer-ternary': 'off',
|
||||||
'yield-star-spacing': [ 'error', 'after' ],
|
'yield-star-spacing': [ 'error', 'after' ],
|
||||||
|
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,15 +1,22 @@
|
|||||||
|
#### 📁 Related issues
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Things to check before submitting a pull request:
|
Reference any relevant issues here. Closing keywords only have an effect when targeting the main branch. If there are no related issues, you must first create an issue through https://github.com/solid/community-server/issues/new/choose
|
||||||
* Label this PR with the correct semver label (if you have permission to do so).
|
-->
|
||||||
|
|
||||||
|
#### ✍️ Description
|
||||||
|
|
||||||
|
<!-- Describe the relevant changes in this PR. Also add notes that might be relevant for code reviewers. -->
|
||||||
|
|
||||||
|
|
||||||
|
### ✅ PR check list
|
||||||
|
|
||||||
|
Before this pull request can be merged, a core maintainer will check whether
|
||||||
|
* [ ] this PR is labeled with the correct semver label
|
||||||
- semver.patch: Backwards compatible bug fixes.
|
- semver.patch: Backwards compatible bug fixes.
|
||||||
- semver.minor: Backwards compatible feature additions.
|
- semver.minor: Backwards compatible feature additions.
|
||||||
- semver.major: Breaking changes. This includes changing interfaces or configuration behaviour.
|
- semver.major: Breaking changes. This includes changing interfaces or configuration behaviour.
|
||||||
* Target the correct branch. Patch updates can target main, other changes should target the latest versions/* branch.
|
* [ ] the correct branch is targeted. Patch updates can target main, other changes should target the latest versions/* branch.
|
||||||
* Update the RELEASE_NOTES.md document in case of relevant feature or config changes.
|
* [ ] the RELEASE_NOTES.md document in case of relevant feature or config changes.
|
||||||
-->
|
|
||||||
|
|
||||||
#### Related issues
|
<!-- Try to check these to the best of your abilities before opening the PR -->
|
||||||
<!-- Reference any relevant issues here. Closing keywords only have an effect when targeting the main branch. -->
|
|
||||||
|
|
||||||
#### Description
|
|
||||||
<!-- Describe the relevant changes in this PR. Also add notes that might be relevant for code reviewers. -->
|
|
||||||
|
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -139,6 +139,46 @@ jobs:
|
|||||||
github-token: ${{ secrets.github_token }}
|
github-token: ${{ secrets.github_token }}
|
||||||
parallel-finished: true
|
parallel-finished: true
|
||||||
|
|
||||||
|
docker:
|
||||||
|
needs:
|
||||||
|
- lint
|
||||||
|
- test-unit
|
||||||
|
- test-integration
|
||||||
|
- test-integration-windows
|
||||||
|
- validate-components
|
||||||
|
# Only run on tags starting with v prefix for now -- extra push need for triggering CI again
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
solidproject/community-server
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
github-token: ${{ secrets.github_token }}
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
2
.github/workflows/schedule.yml
vendored
2
.github/workflows/schedule.yml
vendored
@ -44,6 +44,6 @@ jobs:
|
|||||||
-v "$(pwd)"/reports/css:/reports
|
-v "$(pwd)"/reports/css:/reports
|
||||||
--env-file=./test/deploy/conformance.env
|
--env-file=./test/deploy/conformance.env
|
||||||
--network="host"
|
--network="host"
|
||||||
solidconformancetestbeta/conformance-test-harness
|
solidproject/conformance-test-harness
|
||||||
--output=/reports
|
--output=/reports
|
||||||
--target=https://github.com/solid/conformance-test-harness/css
|
--target=https://github.com/solid/conformance-test-harness/css
|
||||||
|
15
README.md
15
README.md
@ -69,21 +69,20 @@ npm start -- # add parameters if needed
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 📦 Running via Docker
|
### 📦 Running via Docker
|
||||||
Docker allows you to run the server without having Node.js installed:
|
Docker allows you to run the server without having Node.js installed. Images are built on each tagged version and hosted on [Docker Hub](https://hub.docker.com/r/solidproject/community-server).
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
# Clone the repo to get access to the configs
|
||||||
git clone https://github.com/solid/community-server.git
|
git clone https://github.com/solid/community-server.git
|
||||||
cd community-server
|
cd community-server
|
||||||
# Build the Docker image
|
|
||||||
docker build --rm -f Dockerfile -t css:latest .
|
|
||||||
# Run the image, serving your `~/Solid` directory on `http://localhost:3000`
|
# Run the image, serving your `~/Solid` directory on `http://localhost:3000`
|
||||||
docker run --rm -v ~/Solid:/data -p 3000:3000 -it css:latest
|
docker run --rm -v ~/Solid:/data -p 3000:3000 -it solidproject/community-server:latest
|
||||||
# Or use one of the built-in configurations
|
# Or use one of the built-in configurations
|
||||||
docker run --rm -p 3000:3000 -it css:latest -c config/default.json
|
docker run --rm -p 3000:3000 -it solidproject/community-server -c config/default.json
|
||||||
# Or use your own configuration mapped to the right directory
|
# Or use your own configuration mapped to the right directory
|
||||||
docker run --rm -v ~/solid-config:/config -p 3000:3000 -it css:latest -c /config/my-config.json
|
docker run --rm -v ~/solid-config:/config -p 3000:3000 -it solidproject/community-server -c /config/my-config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 🔧 Configuring the server
|
## 🔧 Configuring the server
|
||||||
The Community Solid Server is designed to be flexible
|
The Community Solid Server is designed to be flexible
|
||||||
such that people can easily run different configurations.
|
such that people can easily run different configurations.
|
||||||
@ -131,7 +130,7 @@ the [📐 architectural diagram](https://rubenverborgh.github.io/solid-server-a
|
|||||||
can help you find your way.
|
can help you find your way.
|
||||||
|
|
||||||
If you want to help out with server development,
|
If you want to help out with server development,
|
||||||
have a look at the [📓 developer notes](guides/developer-notes.md) and
|
have a look at the [📓 developer notes](https://github.com/solid/community-server/blob/main/guides/developer-notes.md) and
|
||||||
[🛠️ good first issues](https://github.com/solid/community-server/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
[🛠️ good first issues](https://github.com/solid/community-server/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
"BasicInteractionRoute:_controls_key": "forgotPassword",
|
"BasicInteractionRoute:_controls_key": "forgotPassword",
|
||||||
"BasicInteractionRoute:_controls_value": "/forgotpassword"
|
"BasicInteractionRoute:_controls_value": "/forgotpassword/"
|
||||||
},
|
},
|
||||||
"handler": {
|
"handler": {
|
||||||
"@type": "ForgotPasswordHandler",
|
"@type": "ForgotPasswordHandler",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
"BasicInteractionRoute:_controls_key": "login",
|
"BasicInteractionRoute:_controls_key": "login",
|
||||||
"BasicInteractionRoute:_controls_value": "/login"
|
"BasicInteractionRoute:_controls_value": "/login/"
|
||||||
},
|
},
|
||||||
"handler": {
|
"handler": {
|
||||||
"@type": "LoginHandler",
|
"@type": "LoginHandler",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
"BasicInteractionRoute:_controls_key": "register",
|
"BasicInteractionRoute:_controls_key": "register",
|
||||||
"BasicInteractionRoute:_controls_value": "/register"
|
"BasicInteractionRoute:_controls_value": "/register/"
|
||||||
},
|
},
|
||||||
"handler": {
|
"handler": {
|
||||||
"@type": "RegistrationHandler",
|
"@type": "RegistrationHandler",
|
||||||
|
3308
package-lock.json
generated
3308
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -77,7 +77,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@comunica/actor-init-sparql": "^1.21.3",
|
"@comunica/actor-init-sparql": "^1.21.3",
|
||||||
"@rdfjs/data-model": "^1.2.0",
|
"@rdfjs/data-model": "^1.2.0",
|
||||||
"@solid/access-token-verifier": "^1.0.1",
|
"@solid/access-token-verifier": "^1.1.2",
|
||||||
"@types/arrayify-stream": "^1.0.0",
|
"@types/arrayify-stream": "^1.0.0",
|
||||||
"@types/async-lock": "^1.1.2",
|
"@types/async-lock": "^1.1.2",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
@ -87,7 +87,7 @@
|
|||||||
"@types/marked": "^3.0.0",
|
"@types/marked": "^3.0.0",
|
||||||
"@types/mime-types": "^2.1.0",
|
"@types/mime-types": "^2.1.0",
|
||||||
"@types/n3": "^1.10.0",
|
"@types/n3": "^1.10.0",
|
||||||
"@types/node": "^15.12.5",
|
"@types/node": "^14.18.0",
|
||||||
"@types/nodemailer": "^6.4.2",
|
"@types/nodemailer": "^6.4.2",
|
||||||
"@types/pump": "^1.1.1",
|
"@types/pump": "^1.1.1",
|
||||||
"@types/punycode": "^2.1.0",
|
"@types/punycode": "^2.1.0",
|
||||||
@ -109,11 +109,11 @@
|
|||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"fetch-sparql-endpoint": "^2.0.1",
|
"fetch-sparql-endpoint": "^2.0.1",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"jose": "^3.11.6",
|
"jose": "^4.3.7",
|
||||||
"lodash.orderby": "^4.6.0",
|
"lodash.orderby": "^4.6.0",
|
||||||
"marked": "^3.0.0",
|
"marked": "^3.0.0",
|
||||||
"mime-types": "^2.1.32",
|
"mime-types": "^2.1.32",
|
||||||
"n3": "^1.10.0",
|
"n3": "^1.12.2",
|
||||||
"nodemailer": "^6.6.2",
|
"nodemailer": "^6.6.2",
|
||||||
"oidc-provider": "^6.31.1",
|
"oidc-provider": "^6.31.1",
|
||||||
"pump": "^3.0.0",
|
"pump": "^3.0.0",
|
||||||
@ -142,17 +142,17 @@
|
|||||||
"@types/jest": "^27.0.0",
|
"@types/jest": "^27.0.0",
|
||||||
"@types/set-cookie-parser": "^2.4.0",
|
"@types/set-cookie-parser": "^2.4.0",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.1",
|
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||||
"@typescript-eslint/parser": "^4.28.1",
|
"@typescript-eslint/parser": "^5.3.0",
|
||||||
"cheerio": "^1.0.0-rc.10",
|
"cheerio": "^1.0.0-rc.10",
|
||||||
"componentsjs-generator": "^2.6.0",
|
"componentsjs-generator": "^2.6.0",
|
||||||
"eslint": "^7.29.0",
|
"eslint": "^8.4.1",
|
||||||
"eslint-config-es": "^3.20.3",
|
"eslint-config-es": "4.1.0",
|
||||||
"eslint-import-resolver-typescript": "^2.4.0",
|
"eslint-import-resolver-typescript": "^2.5.0",
|
||||||
"eslint-plugin-import": "^2.23.4",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-jest": "^24.3.6",
|
"eslint-plugin-jest": "^25.3.0",
|
||||||
"eslint-plugin-tsdoc": "^0.2.14",
|
"eslint-plugin-tsdoc": "^0.2.14",
|
||||||
"eslint-plugin-unused-imports": "^1.1.1",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
"jest": "^27.0.6",
|
"jest": "^27.0.6",
|
||||||
|
@ -19,7 +19,7 @@ export class BearerWebIdExtractor extends CredentialsExtractor {
|
|||||||
|
|
||||||
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
||||||
const { authorization } = headers;
|
const { authorization } = headers;
|
||||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
if (!authorization || !/^Bearer /ui.test(authorization)) {
|
||||||
throw new NotImplementedHttpError('No Bearer Authorization header specified.');
|
throw new NotImplementedHttpError('No Bearer Authorization header specified.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
|
|||||||
|
|
||||||
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
||||||
const { authorization } = headers;
|
const { authorization } = headers;
|
||||||
if (!authorization || !authorization.startsWith('DPoP ')) {
|
if (!authorization || !/^DPoP /ui.test(authorization)) {
|
||||||
throw new NotImplementedHttpError('No DPoP-bound Authorization header specified.');
|
throw new NotImplementedHttpError('No DPoP-bound Authorization header specified.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,13 @@ export class UnsecureWebIdExtractor extends CredentialsExtractor {
|
|||||||
|
|
||||||
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
public async canHandle({ headers }: HttpRequest): Promise<void> {
|
||||||
const { authorization } = headers;
|
const { authorization } = headers;
|
||||||
if (!authorization || !authorization.startsWith('WebID ')) {
|
if (!authorization || !/^WebID /ui.test(authorization)) {
|
||||||
throw new NotImplementedHttpError('No WebID Authorization header specified.');
|
throw new NotImplementedHttpError('No WebID Authorization header specified.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle({ headers }: HttpRequest): Promise<CredentialSet> {
|
public async handle({ headers }: HttpRequest): Promise<CredentialSet> {
|
||||||
const webId = /^WebID\s+(.*)/u.exec(headers.authorization!)![1];
|
const webId = /^WebID\s+(.*)/ui.exec(headers.authorization!)![1];
|
||||||
this.logger.info(`Agent unsecurely claims to be ${webId}`);
|
this.logger.info(`Agent unsecurely claims to be ${webId}`);
|
||||||
return { [CredentialGroup.agent]: { webId }};
|
return { [CredentialGroup.agent]: { webId }};
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention, import/no-unresolved, tsdoc/syntax */
|
/* eslint-disable @typescript-eslint/naming-convention, tsdoc/syntax */
|
||||||
// import/no-unresolved can't handle jose imports
|
// import/no-unresolved can't handle jose imports
|
||||||
// tsdoc/syntax can't handle {json} parameter
|
// tsdoc/syntax can't handle {json} parameter
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import type { JWK } from 'jose/jwk/from_key_like';
|
import type { JWK } from 'jose';
|
||||||
import { fromKeyLike } from 'jose/jwk/from_key_like';
|
import { exportJWK, generateKeyPair } from 'jose';
|
||||||
import { generateKeyPair } from 'jose/util/generate_key_pair';
|
|
||||||
import type { AnyObject,
|
import type { AnyObject,
|
||||||
CanBePromise,
|
CanBePromise,
|
||||||
KoaContextWithOIDC,
|
KoaContextWithOIDC,
|
||||||
@ -135,7 +134,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
|||||||
// Cast necessary due to typing conflict between jose 2.x and 3.x
|
// Cast necessary due to typing conflict between jose 2.x and 3.x
|
||||||
config.jwks = await this.generateJwks() as any;
|
config.jwks = await this.generateJwks() as any;
|
||||||
config.cookies = {
|
config.cookies = {
|
||||||
...config.cookies ?? {},
|
...config.cookies,
|
||||||
keys: await this.generateCookieKeys(),
|
keys: await this.generateCookieKeys(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -154,7 +153,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
|||||||
}
|
}
|
||||||
// If they are not, generate and save them
|
// If they are not, generate and save them
|
||||||
const { privateKey } = await generateKeyPair('RS256');
|
const { privateKey } = await generateKeyPair('RS256');
|
||||||
const jwk = await fromKeyLike(privateKey);
|
const jwk = await exportJWK(privateKey);
|
||||||
// Required for Solid authn client
|
// Required for Solid authn client
|
||||||
jwk.alg = 'RS256';
|
jwk.alg = 'RS256';
|
||||||
// In node v15.12.0 the JWKS does not get accepted because the JWK is not a plain object,
|
// In node v15.12.0 the JWKS does not get accepted because the JWK is not a plain object,
|
||||||
|
@ -122,11 +122,11 @@ describe('A quota server', (): void => {
|
|||||||
|
|
||||||
const response1 = performSimplePutWithLength(testFile1, 2000);
|
const response1 = performSimplePutWithLength(testFile1, 2000);
|
||||||
await expect(response1).resolves.toBeDefined();
|
await expect(response1).resolves.toBeDefined();
|
||||||
expect((await response1).status).toEqual(201);
|
expect((await response1).status).toBe(201);
|
||||||
|
|
||||||
const response2 = performSimplePutWithLength(testFile2, 2500);
|
const response2 = performSimplePutWithLength(testFile2, 2500);
|
||||||
await expect(response2).resolves.toBeDefined();
|
await expect(response2).resolves.toBeDefined();
|
||||||
expect((await response2).status).toEqual(413);
|
expect((await response2).status).toBe(413);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test if writing in another pod is still possible
|
// Test if writing in another pod is still possible
|
||||||
@ -135,7 +135,7 @@ describe('A quota server', (): void => {
|
|||||||
|
|
||||||
const response1 = performSimplePutWithLength(testFile1, 2000);
|
const response1 = performSimplePutWithLength(testFile1, 2000);
|
||||||
await expect(response1).resolves.toBeDefined();
|
await expect(response1).resolves.toBeDefined();
|
||||||
expect((await response1).status).toEqual(201);
|
expect((await response1).status).toBe(201);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Both pods should not accept this request anymore
|
// Both pods should not accept this request anymore
|
||||||
@ -145,11 +145,11 @@ describe('A quota server', (): void => {
|
|||||||
|
|
||||||
const response1 = performSimplePutWithLength(testFile1, 2500);
|
const response1 = performSimplePutWithLength(testFile1, 2500);
|
||||||
await expect(response1).resolves.toBeDefined();
|
await expect(response1).resolves.toBeDefined();
|
||||||
expect((await response1).status).toEqual(413);
|
expect((await response1).status).toBe(413);
|
||||||
|
|
||||||
const response2 = performSimplePutWithLength(testFile2, 2500);
|
const response2 = performSimplePutWithLength(testFile2, 2500);
|
||||||
await expect(response2).resolves.toBeDefined();
|
await expect(response2).resolves.toBeDefined();
|
||||||
expect((await response2).status).toEqual(413);
|
expect((await response2).status).toBe(413);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -196,12 +196,12 @@ describe('A quota server', (): void => {
|
|||||||
const response1 = performSimplePutWithLength(testFile1, 2000);
|
const response1 = performSimplePutWithLength(testFile1, 2000);
|
||||||
await expect(response1).resolves.toBeDefined();
|
await expect(response1).resolves.toBeDefined();
|
||||||
const awaitedRes1 = await response1;
|
const awaitedRes1 = await response1;
|
||||||
expect(awaitedRes1.status).toEqual(201);
|
expect(awaitedRes1.status).toBe(201);
|
||||||
|
|
||||||
const response2 = performSimplePutWithLength(testFile2, 2500);
|
const response2 = performSimplePutWithLength(testFile2, 2500);
|
||||||
await expect(response2).resolves.toBeDefined();
|
await expect(response2).resolves.toBeDefined();
|
||||||
const awaitedRes2 = await response2;
|
const awaitedRes2 = await response2;
|
||||||
expect(awaitedRes2.status).toEqual(413);
|
expect(awaitedRes2.status).toBe(413);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return 413 when trying to write to any pod when global quota is exceeded.', async(): Promise<void> => {
|
it('should return 413 when trying to write to any pod when global quota is exceeded.', async(): Promise<void> => {
|
||||||
@ -211,12 +211,12 @@ describe('A quota server', (): void => {
|
|||||||
const response1 = performSimplePutWithLength(testFile1, 2500);
|
const response1 = performSimplePutWithLength(testFile1, 2500);
|
||||||
await expect(response1).resolves.toBeDefined();
|
await expect(response1).resolves.toBeDefined();
|
||||||
const awaitedRes1 = await response1;
|
const awaitedRes1 = await response1;
|
||||||
expect(awaitedRes1.status).toEqual(413);
|
expect(awaitedRes1.status).toBe(413);
|
||||||
|
|
||||||
const response2 = performSimplePutWithLength(testFile2, 2500);
|
const response2 = performSimplePutWithLength(testFile2, 2500);
|
||||||
await expect(response2).resolves.toBeDefined();
|
await expect(response2).resolves.toBeDefined();
|
||||||
const awaitedRes2 = await response2;
|
const awaitedRes2 = await response2;
|
||||||
expect(awaitedRes2.status).toEqual(413);
|
expect(awaitedRes2.status).toBe(413);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -53,7 +53,7 @@ describe('A BasicRequestParser with simple input parsers', (): void => {
|
|||||||
metadata: expect.any(RepresentationMetadata),
|
metadata: expect.any(RepresentationMetadata),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result.body?.metadata.contentType).toEqual('text/turtle');
|
expect(result.body?.metadata.contentType).toBe('text/turtle');
|
||||||
|
|
||||||
await expect(arrayifyStream(result.body.data)).resolves.toEqual(
|
await expect(arrayifyStream(result.body.data)).resolves.toEqual(
|
||||||
[ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ],
|
[ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ],
|
||||||
|
@ -62,6 +62,21 @@ describe('A BearerWebIdExtractor', (): void => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('on a request with Authorization and a lowercase Bearer token', (): void => {
|
||||||
|
const request = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
authorization: 'bearer token-1234',
|
||||||
|
},
|
||||||
|
} as any as HttpRequest;
|
||||||
|
|
||||||
|
it('calls the Bearer verifier with the correct parameters.', async(): Promise<void> => {
|
||||||
|
await webIdExtractor.handleSafe(request);
|
||||||
|
expect(solidTokenVerifier).toHaveBeenCalledTimes(1);
|
||||||
|
expect(solidTokenVerifier).toHaveBeenCalledWith('bearer token-1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when verification throws an error', (): void => {
|
describe('when verification throws an error', (): void => {
|
||||||
const request = {
|
const request = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -90,6 +90,22 @@ describe('A DPoPWebIdExtractor', (): void => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('on a request with Authorization specifying DPoP in lowercase', (): void => {
|
||||||
|
const request = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
authorization: 'dpop token-1234',
|
||||||
|
dpop: 'token-5678',
|
||||||
|
},
|
||||||
|
} as any as HttpRequest;
|
||||||
|
|
||||||
|
it('calls the target extractor with the correct parameters.', async(): Promise<void> => {
|
||||||
|
await webIdExtractor.handleSafe(request);
|
||||||
|
expect(targetExtractor.handle).toHaveBeenCalledTimes(1);
|
||||||
|
expect(targetExtractor.handle).toHaveBeenCalledWith({ request });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when verification throws an error', (): void => {
|
describe('when verification throws an error', (): void => {
|
||||||
const request = {
|
const request = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -20,9 +20,15 @@ describe('An UnsecureWebIdExtractor', (): void => {
|
|||||||
await expect(result).rejects.toThrow('No WebID Authorization header specified.');
|
await expect(result).rejects.toThrow('No WebID Authorization header specified.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the authorization header as WebID if there is one.', async(): Promise<void> => {
|
it('returns the authorization header as WebID if specified.', async(): Promise<void> => {
|
||||||
const headers = { authorization: 'WebID http://alice.example/card#me' };
|
const headers = { authorization: 'WebID http://alice.example/card#me' };
|
||||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||||
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns the authorization header as WebID if specified with a lowercase token.', async(): Promise<void> => {
|
||||||
|
const headers = { authorization: 'webid http://alice.example/card#me' };
|
||||||
|
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||||
|
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -49,7 +49,7 @@ describe('A BasicResponseWriter', (): void => {
|
|||||||
response.on('end', (): void => {
|
response.on('end', (): void => {
|
||||||
expect(response._isEndCalled()).toBeTruthy();
|
expect(response._isEndCalled()).toBeTruthy();
|
||||||
expect(response._getStatusCode()).toBe(201);
|
expect(response._getStatusCode()).toBe(201);
|
||||||
expect(response._getData()).toEqual('<http://test.com/s> <http://test.com/p> <http://test.com/o>.');
|
expect(response._getData()).toBe('<http://test.com/s> <http://test.com/p> <http://test.com/o>.');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -35,7 +35,7 @@ describe('A RepresentationMetadata', (): void => {
|
|||||||
describe('constructor', (): void => {
|
describe('constructor', (): void => {
|
||||||
it('creates a blank node if no identifier was given.', async(): Promise<void> => {
|
it('creates a blank node if no identifier was given.', async(): Promise<void> => {
|
||||||
metadata = new RepresentationMetadata();
|
metadata = new RepresentationMetadata();
|
||||||
expect(metadata.identifier.termType).toEqual('BlankNode');
|
expect(metadata.identifier.termType).toBe('BlankNode');
|
||||||
expect(metadata.quads()).toHaveLength(0);
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,19 +51,19 @@ describe('A RepresentationMetadata', (): void => {
|
|||||||
|
|
||||||
it('converts string to content type.', async(): Promise<void> => {
|
it('converts string to content type.', async(): Promise<void> => {
|
||||||
metadata = new RepresentationMetadata('text/turtle');
|
metadata = new RepresentationMetadata('text/turtle');
|
||||||
expect(metadata.contentType).toEqual('text/turtle');
|
expect(metadata.contentType).toBe('text/turtle');
|
||||||
|
|
||||||
metadata = new RepresentationMetadata({ path: 'identifier' }, 'text/turtle');
|
metadata = new RepresentationMetadata({ path: 'identifier' }, 'text/turtle');
|
||||||
expect(metadata.contentType).toEqual('text/turtle');
|
expect(metadata.contentType).toBe('text/turtle');
|
||||||
|
|
||||||
metadata = new RepresentationMetadata(new RepresentationMetadata(), 'text/turtle');
|
metadata = new RepresentationMetadata(new RepresentationMetadata(), 'text/turtle');
|
||||||
expect(metadata.contentType).toEqual('text/turtle');
|
expect(metadata.contentType).toBe('text/turtle');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stores the content-length correctly.', async(): Promise<void> => {
|
it('stores the content-length correctly.', async(): Promise<void> => {
|
||||||
metadata = new RepresentationMetadata();
|
metadata = new RepresentationMetadata();
|
||||||
metadata.contentLength = 50;
|
metadata.contentLength = 50;
|
||||||
expect(metadata.contentLength).toEqual(50);
|
expect(metadata.contentLength).toBe(50);
|
||||||
|
|
||||||
metadata = new RepresentationMetadata();
|
metadata = new RepresentationMetadata();
|
||||||
metadata.contentLength = undefined;
|
metadata.contentLength = undefined;
|
||||||
@ -285,7 +285,7 @@ describe('A RepresentationMetadata', (): void => {
|
|||||||
expect(metadata.contentType).toBeUndefined();
|
expect(metadata.contentType).toBeUndefined();
|
||||||
metadata.contentType = 'a/b';
|
metadata.contentType = 'a/b';
|
||||||
expect(metadata.get(CONTENT_TYPE)).toEqualRdfTerm(literal('a/b'));
|
expect(metadata.get(CONTENT_TYPE)).toEqualRdfTerm(literal('a/b'));
|
||||||
expect(metadata.contentType).toEqual('a/b');
|
expect(metadata.contentType).toBe('a/b');
|
||||||
metadata.contentType = undefined;
|
metadata.contentType = undefined;
|
||||||
expect(metadata.contentType).toBeUndefined();
|
expect(metadata.contentType).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
@ -98,7 +98,7 @@ describe('An IdentityProviderFactory', (): void => {
|
|||||||
expect(config.jwks).toEqual({ keys: [ expect.objectContaining({ kty: 'RSA' }) ]});
|
expect(config.jwks).toEqual({ keys: [ expect.objectContaining({ kty: 'RSA' }) ]});
|
||||||
expect(config.routes).toEqual(routes);
|
expect(config.routes).toEqual(routes);
|
||||||
|
|
||||||
expect((config.interactions?.url as any)()).toEqual('/idp/');
|
expect((config.interactions?.url as any)()).toBe('/idp/');
|
||||||
expect((config.audiences as any)(null, null, {}, 'access_token')).toBe('solid');
|
expect((config.audiences as any)(null, null, {}, 'access_token')).toBe('solid');
|
||||||
expect((config.audiences as any)(null, null, { clientId: 'clientId' }, 'client_credentials')).toBe('clientId');
|
expect((config.audiences as any)(null, null, { clientId: 'clientId' }, 'client_credentials')).toBe('clientId');
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ describe('LogUtil', (): void => {
|
|||||||
|
|
||||||
it('allows creating a lazy logger for a string label.', async(): Promise<void> => {
|
it('allows creating a lazy logger for a string label.', async(): Promise<void> => {
|
||||||
expect(getLoggerFor('MyLabel')).toBeInstanceOf(LazyLogger);
|
expect(getLoggerFor('MyLabel')).toBeInstanceOf(LazyLogger);
|
||||||
expect((getLoggerFor('MyLabel') as any).label).toEqual('MyLabel');
|
expect((getLoggerFor('MyLabel') as any).label).toBe('MyLabel');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows creating a lazy logger for a class instance.', async(): Promise<void> => {
|
it('allows creating a lazy logger for a class instance.', async(): Promise<void> => {
|
||||||
expect(getLoggerFor(new VoidLogger())).toBeInstanceOf(LazyLogger);
|
expect(getLoggerFor(new VoidLogger())).toBeInstanceOf(LazyLogger);
|
||||||
expect((getLoggerFor(new VoidLogger()) as any).label).toEqual('VoidLogger');
|
expect((getLoggerFor(new VoidLogger()) as any).label).toBe('VoidLogger');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows setting the global logger factory.', async(): Promise<void> => {
|
it('allows setting the global logger factory.', async(): Promise<void> => {
|
||||||
|
@ -13,7 +13,7 @@ describe('WinstonLoggerFactory', (): void => {
|
|||||||
const logger = factory.createLogger('MyLabel');
|
const logger = factory.createLogger('MyLabel');
|
||||||
expect(logger).toBeInstanceOf(WinstonLogger);
|
expect(logger).toBeInstanceOf(WinstonLogger);
|
||||||
const innerLogger: Logger = (logger as any).logger;
|
const innerLogger: Logger = (logger as any).logger;
|
||||||
expect(innerLogger.level).toEqual('debug');
|
expect(innerLogger.level).toBe('debug');
|
||||||
expect(innerLogger.format).toBeTruthy();
|
expect(innerLogger.format).toBeTruthy();
|
||||||
expect(innerLogger.transports).toHaveLength(1);
|
expect(innerLogger.transports).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
@ -180,7 +180,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
const result = await store.getRepresentation(resourceID);
|
const result = await store.getRepresentation(resourceID);
|
||||||
expect(result).toMatchObject({ binary: true });
|
expect(result).toMatchObject({ binary: true });
|
||||||
expect(await arrayifyStream(result.data)).toEqual([ resourceData ]);
|
expect(await arrayifyStream(result.data)).toEqual([ resourceData ]);
|
||||||
expect(result.metadata.contentType).toEqual('text/plain');
|
expect(result.metadata.contentType).toBe('text/plain');
|
||||||
expect(result.metadata.get('AUXILIARY')?.value).toBe(auxiliaryStrategy.getAuxiliaryIdentifier(resourceID).path);
|
expect(result.metadata.get('AUXILIARY')?.value).toBe(auxiliaryStrategy.getAuxiliaryIdentifier(resourceID).path);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -690,7 +690,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
{ path: root },
|
{ path: root },
|
||||||
]);
|
]);
|
||||||
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
||||||
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
|
expect(accessor.data[`${root}resource.dummy`]).toBeDefined();
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
expect(logger.error).toHaveBeenLastCalledWith(
|
expect(logger.error).toHaveBeenLastCalledWith(
|
||||||
'Error deleting auxiliary resource http://test.com/resource.dummy: auxiliary error!',
|
'Error deleting auxiliary resource http://test.com/resource.dummy: auxiliary error!',
|
||||||
|
@ -109,16 +109,16 @@ describe('ConversionUtil', (): void => {
|
|||||||
describe('#matchesMediaPreferences', (): void => {
|
describe('#matchesMediaPreferences', (): void => {
|
||||||
it('returns false if there are no matches.', async(): Promise<void> => {
|
it('returns false if there are no matches.', async(): Promise<void> => {
|
||||||
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 };
|
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 };
|
||||||
expect(matchesMediaPreferences('c/x', preferences)).toEqual(false);
|
expect(matchesMediaPreferences('c/x', preferences)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if there are matches.', async(): Promise<void> => {
|
it('returns true if there are matches.', async(): Promise<void> => {
|
||||||
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 };
|
const preferences: ValuePreferences = { 'a/x': 1, 'b/x': 0.5, 'c/x': 0 };
|
||||||
expect(matchesMediaPreferences('b/x', preferences)).toEqual(true);
|
expect(matchesMediaPreferences('b/x', preferences)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches anything if there are no preferences.', async(): Promise<void> => {
|
it('matches anything if there are no preferences.', async(): Promise<void> => {
|
||||||
expect(matchesMediaPreferences('a/a')).toEqual(true);
|
expect(matchesMediaPreferences('a/a')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not match internal types if not in the preferences.', async(): Promise<void> => {
|
it('does not match internal types if not in the preferences.', async(): Promise<void> => {
|
||||||
@ -157,7 +157,7 @@ describe('ConversionUtil', (): void => {
|
|||||||
describe('#preferencesToString', (): void => {
|
describe('#preferencesToString', (): void => {
|
||||||
it('returns a string serialization.', async(): Promise<void> => {
|
it('returns a string serialization.', async(): Promise<void> => {
|
||||||
const preferences: ValuePreferences = { 'a/*': 1, 'b/b': 0.8, 'c/c': 0 };
|
const preferences: ValuePreferences = { 'a/*': 1, 'b/b': 0.8, 'c/c': 0 };
|
||||||
expect(preferencesToString(preferences)).toEqual('a/*:1,b/b:0.8,c/c:0');
|
expect(preferencesToString(preferences)).toBe('a/*:1,b/b:0.8,c/c:0');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -55,7 +55,7 @@ describe('A QuadToRdfConverter', (): void => {
|
|||||||
binary: true,
|
binary: true,
|
||||||
metadata: expect.any(RepresentationMetadata),
|
metadata: expect.any(RepresentationMetadata),
|
||||||
});
|
});
|
||||||
expect(result.metadata.contentType).toEqual('text/turtle');
|
expect(result.metadata.contentType).toBe('text/turtle');
|
||||||
await expect(readableToString(result.data)).resolves.toEqual(
|
await expect(readableToString(result.data)).resolves.toEqual(
|
||||||
`<http://test.com/s> <http://test.com/p> <http://test.com/o>.
|
`<http://test.com/s> <http://test.com/p> <http://test.com/o>.
|
||||||
`,
|
`,
|
||||||
@ -73,7 +73,7 @@ describe('A QuadToRdfConverter', (): void => {
|
|||||||
metadata);
|
metadata);
|
||||||
const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }};
|
const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }};
|
||||||
const result = await converter.handle({ identifier, representation, preferences });
|
const result = await converter.handle({ identifier, representation, preferences });
|
||||||
expect(result.metadata.contentType).toEqual('text/turtle');
|
expect(result.metadata.contentType).toBe('text/turtle');
|
||||||
await expect(readableToString(result.data)).resolves.toEqual(
|
await expect(readableToString(result.data)).resolves.toEqual(
|
||||||
`@prefix dc: <http://purl.org/dc/terms/>.
|
`@prefix dc: <http://purl.org/dc/terms/>.
|
||||||
@prefix test: <http://test.com/>.
|
@prefix test: <http://test.com/>.
|
||||||
@ -92,7 +92,7 @@ test:s dc:modified test:o.
|
|||||||
metadata);
|
metadata);
|
||||||
const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }};
|
const preferences: RepresentationPreferences = { type: { 'text/turtle': 1 }};
|
||||||
const result = await converter.handle({ identifier, representation, preferences });
|
const result = await converter.handle({ identifier, representation, preferences });
|
||||||
expect(result.metadata.contentType).toEqual('text/turtle');
|
expect(result.metadata.contentType).toBe('text/turtle');
|
||||||
await expect(readableToString(result.data)).resolves.toEqual(
|
await expect(readableToString(result.data)).resolves.toEqual(
|
||||||
`<> <#abc> <def/ghi>.
|
`<> <#abc> <def/ghi>.
|
||||||
`,
|
`,
|
||||||
@ -113,7 +113,7 @@ test:s dc:modified test:o.
|
|||||||
binary: true,
|
binary: true,
|
||||||
metadata: expect.any(RepresentationMetadata),
|
metadata: expect.any(RepresentationMetadata),
|
||||||
});
|
});
|
||||||
expect(result.metadata.contentType).toEqual('application/ld+json');
|
expect(result.metadata.contentType).toBe('application/ld+json');
|
||||||
await expect(readableToString(result.data)).resolves.toEqual(
|
await expect(readableToString(result.data)).resolves.toEqual(
|
||||||
`[
|
`[
|
||||||
{
|
{
|
||||||
|
@ -43,7 +43,7 @@ describe('A WrappedExpiringStorage', (): void => {
|
|||||||
|
|
||||||
it('returns data if it has not expired.', async(): Promise<void> => {
|
it('returns data if it has not expired.', async(): Promise<void> => {
|
||||||
source.get.mockResolvedValueOnce(createExpires('data!', tomorrow));
|
source.get.mockResolvedValueOnce(createExpires('data!', tomorrow));
|
||||||
await expect(storage.get('key')).resolves.toEqual('data!');
|
await expect(storage.get('key')).resolves.toBe('data!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes expired data when trying to get it.', async(): Promise<void> => {
|
it('deletes expired data when trying to get it.', async(): Promise<void> => {
|
||||||
|
@ -119,7 +119,7 @@ describe('A FileSizeReporter', (): void => {
|
|||||||
it('should return the content-length.', async(): Promise<void> => {
|
it('should return the content-length.', async(): Promise<void> => {
|
||||||
const metadata = new RepresentationMetadata();
|
const metadata = new RepresentationMetadata();
|
||||||
metadata.contentLength = 100;
|
metadata.contentLength = 100;
|
||||||
await expect(fileSizeReporter.estimateSize(metadata)).resolves.toEqual(100);
|
await expect(fileSizeReporter.estimateSize(metadata)).resolves.toBe(100);
|
||||||
});
|
});
|
||||||
it(
|
it(
|
||||||
'should return undefined if no content-length is present in the metadata.',
|
'should return undefined if no content-length is present in the metadata.',
|
||||||
|
@ -25,17 +25,17 @@ import {
|
|||||||
describe('PathUtil', (): void => {
|
describe('PathUtil', (): void => {
|
||||||
describe('#normalizeFilePath', (): void => {
|
describe('#normalizeFilePath', (): void => {
|
||||||
it('normalizes POSIX paths.', async(): Promise<void> => {
|
it('normalizes POSIX paths.', async(): Promise<void> => {
|
||||||
expect(normalizeFilePath('/foo/bar/../baz')).toEqual('/foo/baz');
|
expect(normalizeFilePath('/foo/bar/../baz')).toBe('/foo/baz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes Windows paths.', async(): Promise<void> => {
|
it('normalizes Windows paths.', async(): Promise<void> => {
|
||||||
expect(normalizeFilePath('c:\\foo\\bar\\..\\baz')).toEqual('c:/foo/baz');
|
expect(normalizeFilePath('c:\\foo\\bar\\..\\baz')).toBe('c:/foo/baz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#joinFilePath', (): void => {
|
describe('#joinFilePath', (): void => {
|
||||||
it('joins POSIX paths.', async(): Promise<void> => {
|
it('joins POSIX paths.', async(): Promise<void> => {
|
||||||
expect(joinFilePath('/foo/bar/', '..', '/baz')).toEqual('/foo/baz');
|
expect(joinFilePath('/foo/bar/', '..', '/baz')).toBe('/foo/baz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('joins Windows paths.', async(): Promise<void> => {
|
it('joins Windows paths.', async(): Promise<void> => {
|
||||||
@ -45,11 +45,11 @@ describe('PathUtil', (): void => {
|
|||||||
|
|
||||||
describe('#absoluteFilePath', (): void => {
|
describe('#absoluteFilePath', (): void => {
|
||||||
it('does not change absolute posix paths.', async(): Promise<void> => {
|
it('does not change absolute posix paths.', async(): Promise<void> => {
|
||||||
expect(absoluteFilePath('/foo/bar/')).toEqual('/foo/bar/');
|
expect(absoluteFilePath('/foo/bar/')).toBe('/foo/bar/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts absolute win32 paths to posix paths.', async(): Promise<void> => {
|
it('converts absolute win32 paths to posix paths.', async(): Promise<void> => {
|
||||||
expect(absoluteFilePath('C:\\foo\\bar')).toEqual('C:/foo/bar');
|
expect(absoluteFilePath('C:\\foo\\bar')).toBe('C:/foo/bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes relative paths absolute.', async(): Promise<void> => {
|
it('makes relative paths absolute.', async(): Promise<void> => {
|
||||||
@ -59,70 +59,70 @@ describe('PathUtil', (): void => {
|
|||||||
|
|
||||||
describe('#ensureTrailingSlash', (): void => {
|
describe('#ensureTrailingSlash', (): void => {
|
||||||
it('makes sure there is always exactly 1 slash.', async(): Promise<void> => {
|
it('makes sure there is always exactly 1 slash.', async(): Promise<void> => {
|
||||||
expect(ensureTrailingSlash('http://test.com')).toEqual('http://test.com/');
|
expect(ensureTrailingSlash('http://test.com')).toBe('http://test.com/');
|
||||||
expect(ensureTrailingSlash('http://test.com/')).toEqual('http://test.com/');
|
expect(ensureTrailingSlash('http://test.com/')).toBe('http://test.com/');
|
||||||
expect(ensureTrailingSlash('http://test.com//')).toEqual('http://test.com/');
|
expect(ensureTrailingSlash('http://test.com//')).toBe('http://test.com/');
|
||||||
expect(ensureTrailingSlash('http://test.com///')).toEqual('http://test.com/');
|
expect(ensureTrailingSlash('http://test.com///')).toBe('http://test.com/');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#trimTrailingSlashes', (): void => {
|
describe('#trimTrailingSlashes', (): void => {
|
||||||
it('removes all trailing slashes.', async(): Promise<void> => {
|
it('removes all trailing slashes.', async(): Promise<void> => {
|
||||||
expect(trimTrailingSlashes('http://test.com')).toEqual('http://test.com');
|
expect(trimTrailingSlashes('http://test.com')).toBe('http://test.com');
|
||||||
expect(trimTrailingSlashes('http://test.com/')).toEqual('http://test.com');
|
expect(trimTrailingSlashes('http://test.com/')).toBe('http://test.com');
|
||||||
expect(trimTrailingSlashes('http://test.com//')).toEqual('http://test.com');
|
expect(trimTrailingSlashes('http://test.com//')).toBe('http://test.com');
|
||||||
expect(trimTrailingSlashes('http://test.com///')).toEqual('http://test.com');
|
expect(trimTrailingSlashes('http://test.com///')).toBe('http://test.com');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getExtension', (): void => {
|
describe('#getExtension', (): void => {
|
||||||
it('returns the extension of a path.', async(): Promise<void> => {
|
it('returns the extension of a path.', async(): Promise<void> => {
|
||||||
expect(getExtension('/a/b.txt')).toEqual('txt');
|
expect(getExtension('/a/b.txt')).toBe('txt');
|
||||||
expect(getExtension('/a/btxt')).toEqual('');
|
expect(getExtension('/a/btxt')).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#toCanonicalUriPath', (): void => {
|
describe('#toCanonicalUriPath', (): void => {
|
||||||
it('encodes only the necessary parts.', async(): Promise<void> => {
|
it('encodes only the necessary parts.', async(): Promise<void> => {
|
||||||
expect(toCanonicalUriPath('/a%20path&/name')).toEqual('/a%20path%26/name');
|
expect(toCanonicalUriPath('/a%20path&/name')).toBe('/a%20path%26/name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves the query string untouched.', async(): Promise<void> => {
|
it('leaves the query string untouched.', async(): Promise<void> => {
|
||||||
expect(toCanonicalUriPath('/a%20path&/name?abc=def&xyz')).toEqual('/a%20path%26/name?abc=def&xyz');
|
expect(toCanonicalUriPath('/a%20path&/name?abc=def&xyz')).toBe('/a%20path%26/name?abc=def&xyz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#decodeUriPathComponents', (): void => {
|
describe('#decodeUriPathComponents', (): void => {
|
||||||
it('decodes all parts of a path.', async(): Promise<void> => {
|
it('decodes all parts of a path.', async(): Promise<void> => {
|
||||||
expect(decodeUriPathComponents('/a%20path&/name')).toEqual('/a path&/name');
|
expect(decodeUriPathComponents('/a%20path&/name')).toBe('/a path&/name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves the query string untouched.', async(): Promise<void> => {
|
it('leaves the query string untouched.', async(): Promise<void> => {
|
||||||
expect(decodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toEqual('/a path&/name?abc=def&xyz');
|
expect(decodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toBe('/a path&/name?abc=def&xyz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#encodeUriPathComponents', (): void => {
|
describe('#encodeUriPathComponents', (): void => {
|
||||||
it('encodes all parts of a path.', async(): Promise<void> => {
|
it('encodes all parts of a path.', async(): Promise<void> => {
|
||||||
expect(encodeUriPathComponents('/a%20path&/name')).toEqual('/a%2520path%26/name');
|
expect(encodeUriPathComponents('/a%20path&/name')).toBe('/a%2520path%26/name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves the query string untouched.', async(): Promise<void> => {
|
it('leaves the query string untouched.', async(): Promise<void> => {
|
||||||
expect(encodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toEqual('/a%2520path%26/name?abc=def&xyz');
|
expect(encodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toBe('/a%2520path%26/name?abc=def&xyz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#isContainerPath', (): void => {
|
describe('#isContainerPath', (): void => {
|
||||||
it('returns true if the path ends with a slash.', async(): Promise<void> => {
|
it('returns true if the path ends with a slash.', async(): Promise<void> => {
|
||||||
expect(isContainerPath('/a/b')).toEqual(false);
|
expect(isContainerPath('/a/b')).toBe(false);
|
||||||
expect(isContainerPath('/a/b/')).toEqual(true);
|
expect(isContainerPath('/a/b/')).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#isContainerIdentifier', (): void => {
|
describe('#isContainerIdentifier', (): void => {
|
||||||
it('works af isContainerPath but for identifiers.', async(): Promise<void> => {
|
it('works af isContainerPath but for identifiers.', async(): Promise<void> => {
|
||||||
expect(isContainerIdentifier({ path: '/a/b' })).toEqual(false);
|
expect(isContainerIdentifier({ path: '/a/b' })).toBe(false);
|
||||||
expect(isContainerIdentifier({ path: '/a/b/' })).toEqual(true);
|
expect(isContainerIdentifier({ path: '/a/b/' })).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -159,8 +159,8 @@ describe('PathUtil', (): void => {
|
|||||||
const regex = createSubdomainRegexp('http://test.com/foo/');
|
const regex = createSubdomainRegexp('http://test.com/foo/');
|
||||||
expect(regex.exec('http://test.com/foo/')![1]).toBeUndefined();
|
expect(regex.exec('http://test.com/foo/')![1]).toBeUndefined();
|
||||||
expect(regex.exec('http://test.com/foo/bar')![1]).toBeUndefined();
|
expect(regex.exec('http://test.com/foo/bar')![1]).toBeUndefined();
|
||||||
expect(regex.exec('http://alice.test.com/foo/')![1]).toEqual('alice');
|
expect(regex.exec('http://alice.test.com/foo/')![1]).toBe('alice');
|
||||||
expect(regex.exec('http://alice.bob.test.com/foo/')![1]).toEqual('alice.bob');
|
expect(regex.exec('http://alice.bob.test.com/foo/')![1]).toBe('alice.bob');
|
||||||
expect(regex.exec('http://test.com/')).toBeNull();
|
expect(regex.exec('http://test.com/')).toBeNull();
|
||||||
expect(regex.exec('http://alicetest.com/foo/')).toBeNull();
|
expect(regex.exec('http://alicetest.com/foo/')).toBeNull();
|
||||||
});
|
});
|
||||||
|
@ -9,23 +9,23 @@ describe('PromiseUtil', (): void => {
|
|||||||
const resultInfinite = new Promise<boolean>((): void => {});
|
const resultInfinite = new Promise<boolean>((): void => {});
|
||||||
|
|
||||||
it('returns false if no promise is provided.', async(): Promise<void> => {
|
it('returns false if no promise is provided.', async(): Promise<void> => {
|
||||||
await expect(promiseSome([])).resolves.toEqual(false);
|
await expect(promiseSome([])).resolves.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false if no promise returns true.', async(): Promise<void> => {
|
it('returns false if no promise returns true.', async(): Promise<void> => {
|
||||||
await expect(promiseSome([ resultFalse, resultFalse, resultFalse ])).resolves.toEqual(false);
|
await expect(promiseSome([ resultFalse, resultFalse, resultFalse ])).resolves.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if at least a promise returns true.', async(): Promise<void> => {
|
it('returns true if at least a promise returns true.', async(): Promise<void> => {
|
||||||
await expect(promiseSome([ resultFalse, resultTrue, resultFalse ])).resolves.toEqual(true);
|
await expect(promiseSome([ resultFalse, resultTrue, resultFalse ])).resolves.toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not propagate errors.', async(): Promise<void> => {
|
it('does not propagate errors.', async(): Promise<void> => {
|
||||||
await expect(promiseSome([ resultError, resultFalse, resultFalse ])).resolves.toEqual(false);
|
await expect(promiseSome([ resultError, resultFalse, resultFalse ])).resolves.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with a combination of promises.', async(): Promise<void> => {
|
it('works with a combination of promises.', async(): Promise<void> => {
|
||||||
await expect(promiseSome([ resultError, resultTrue, resultInfinite ])).resolves.toEqual(true);
|
await expect(promiseSome([ resultError, resultTrue, resultInfinite ])).resolves.toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@ describe('StreamUtil', (): void => {
|
|||||||
describe('#readableToString', (): void => {
|
describe('#readableToString', (): void => {
|
||||||
it('concatenates all elements of a Readable.', async(): Promise<void> => {
|
it('concatenates all elements of a Readable.', async(): Promise<void> => {
|
||||||
const stream = Readable.from([ 'a', 'b', 'c' ]);
|
const stream = Readable.from([ 'a', 'b', 'c' ]);
|
||||||
await expect(readableToString(stream)).resolves.toEqual('abc');
|
await expect(readableToString(stream)).resolves.toBe('abc');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ describe('StreamUtil', (): void => {
|
|||||||
const input = Readable.from([ 'data' ]);
|
const input = Readable.from([ 'data' ]);
|
||||||
const output = new PassThrough();
|
const output = new PassThrough();
|
||||||
const piped = pipeSafely(input, output);
|
const piped = pipeSafely(input, output);
|
||||||
await expect(readableToString(piped)).resolves.toEqual('data');
|
await expect(readableToString(piped)).resolves.toBe('data');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pipes errors from one stream to the other.', async(): Promise<void> => {
|
it('pipes errors from one stream to the other.', async(): Promise<void> => {
|
||||||
|
@ -4,14 +4,14 @@ describe('An BadRequestHttpError', (): void => {
|
|||||||
it('has status code 400.', async(): Promise<void> => {
|
it('has status code 400.', async(): Promise<void> => {
|
||||||
const error = new BadRequestHttpError('test');
|
const error = new BadRequestHttpError('test');
|
||||||
|
|
||||||
expect(error.statusCode).toEqual(400);
|
expect(error.statusCode).toBe(400);
|
||||||
expect(error.message).toEqual('test');
|
expect(error.message).toBe('test');
|
||||||
expect(error.name).toEqual('BadRequestHttpError');
|
expect(error.name).toBe('BadRequestHttpError');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has a default message if none was provided.', async(): Promise<void> => {
|
it('has a default message if none was provided.', async(): Promise<void> => {
|
||||||
const error = new BadRequestHttpError();
|
const error = new BadRequestHttpError();
|
||||||
|
|
||||||
expect(error.message).toEqual('The given input is not supported by the server configuration.');
|
expect(error.message).toBe('The given input is not supported by the server configuration.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,7 @@ describe('A StaticHandler', (): void => {
|
|||||||
|
|
||||||
it('returns the stored value.', async(): Promise<void> => {
|
it('returns the stored value.', async(): Promise<void> => {
|
||||||
const handler = new StaticHandler('apple');
|
const handler = new StaticHandler('apple');
|
||||||
await expect(handler.handle()).resolves.toEqual('apple');
|
await expect(handler.handle()).resolves.toBe('apple');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns undefined if there is no stored value.', async(): Promise<void> => {
|
it('returns undefined if there is no stored value.', async(): Promise<void> => {
|
||||||
|
@ -57,7 +57,7 @@ describe('A WaterfallHandler', (): void => {
|
|||||||
it('handles data if a handler supports it.', async(): Promise<void> => {
|
it('handles data if a handler supports it.', async(): Promise<void> => {
|
||||||
const handler = new WaterfallHandler([ handlerFalse, handlerTrue ]);
|
const handler = new WaterfallHandler([ handlerFalse, handlerTrue ]);
|
||||||
|
|
||||||
await expect(handler.handle('test')).resolves.toEqual('test');
|
await expect(handler.handle('test')).resolves.toBe('test');
|
||||||
expect(canHandleFn).toHaveBeenCalledTimes(1);
|
expect(canHandleFn).toHaveBeenCalledTimes(1);
|
||||||
expect(handleFn).toHaveBeenCalledTimes(1);
|
expect(handleFn).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@ -71,7 +71,7 @@ describe('A WaterfallHandler', (): void => {
|
|||||||
it('only calls the canHandle function once of its handlers when handleSafe is called.', async(): Promise<void> => {
|
it('only calls the canHandle function once of its handlers when handleSafe is called.', async(): Promise<void> => {
|
||||||
const handler = new WaterfallHandler([ handlerFalse, handlerTrue ]);
|
const handler = new WaterfallHandler([ handlerFalse, handlerTrue ]);
|
||||||
|
|
||||||
await expect(handler.handleSafe('test')).resolves.toEqual('test');
|
await expect(handler.handleSafe('test')).resolves.toBe('test');
|
||||||
expect(canHandleFn).toHaveBeenCalledTimes(1);
|
expect(canHandleFn).toHaveBeenCalledTimes(1);
|
||||||
expect(handleFn).toHaveBeenCalledTimes(1);
|
expect(handleFn).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
// eslint-disable-next-line import/default
|
|
||||||
import redis from 'redis';
|
import redis from 'redis';
|
||||||
import Redlock from 'redlock';
|
import Redlock from 'redlock';
|
||||||
import type { Lock } from 'redlock';
|
import type { Lock } from 'redlock';
|
||||||
|
@ -25,7 +25,6 @@ describe('A SingleThreadedResourceLocker', (): void => {
|
|||||||
await expect(locker.release(identifier)).rejects.toThrow(InternalServerError);
|
await expect(locker.release(identifier)).rejects.toThrow(InternalServerError);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* eslint-disable jest/valid-expect-in-promise */
|
|
||||||
it('blocks lock acquisition until they are released.', async(): Promise<void> => {
|
it('blocks lock acquisition until they are released.', async(): Promise<void> => {
|
||||||
const results: number[] = [];
|
const results: number[] = [];
|
||||||
const lock1 = locker.acquire(identifier);
|
const lock1 = locker.acquire(identifier);
|
||||||
|
@ -21,7 +21,7 @@ describe('A ChainedTemplateEngine', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('chains the engines.', async(): Promise<void> => {
|
it('chains the engines.', async(): Promise<void> => {
|
||||||
await expect(engine.render(contents, template)).resolves.toEqual('body2');
|
await expect(engine.render(contents, template)).resolves.toBe('body2');
|
||||||
expect(engines[0].render).toHaveBeenCalledTimes(1);
|
expect(engines[0].render).toHaveBeenCalledTimes(1);
|
||||||
expect(engines[0].render).toHaveBeenLastCalledWith(contents, template);
|
expect(engines[0].render).toHaveBeenLastCalledWith(contents, template);
|
||||||
expect(engines[1].render).toHaveBeenCalledTimes(1);
|
expect(engines[1].render).toHaveBeenCalledTimes(1);
|
||||||
@ -30,7 +30,7 @@ describe('A ChainedTemplateEngine', (): void => {
|
|||||||
|
|
||||||
it('can use a different field to pass along the body.', async(): Promise<void> => {
|
it('can use a different field to pass along the body.', async(): Promise<void> => {
|
||||||
engine = new ChainedTemplateEngine(engines, 'different');
|
engine = new ChainedTemplateEngine(engines, 'different');
|
||||||
await expect(engine.render(contents, template)).resolves.toEqual('body2');
|
await expect(engine.render(contents, template)).resolves.toBe('body2');
|
||||||
expect(engines[0].render).toHaveBeenCalledTimes(1);
|
expect(engines[0].render).toHaveBeenCalledTimes(1);
|
||||||
expect(engines[0].render).toHaveBeenLastCalledWith(contents, template);
|
expect(engines[0].render).toHaveBeenLastCalledWith(contents, template);
|
||||||
expect(engines[1].render).toHaveBeenCalledTimes(1);
|
expect(engines[1].render).toHaveBeenCalledTimes(1);
|
||||||
|
@ -38,7 +38,7 @@ export function getPort(name: typeof portNames[number]): number {
|
|||||||
export function describeIf(envFlag: string, name: string, fn: () => void): void {
|
export function describeIf(envFlag: string, name: string, fn: () => void): void {
|
||||||
const flag = `TEST_${envFlag.toUpperCase()}`;
|
const flag = `TEST_${envFlag.toUpperCase()}`;
|
||||||
const enabled = !/^(|0|false)$/iu.test(process.env[flag] ?? '');
|
const enabled = !/^(|0|false)$/iu.test(process.env[flag] ?? '');
|
||||||
// eslint-disable-next-line jest/valid-describe, jest/valid-title, jest/no-disabled-tests
|
// eslint-disable-next-line jest/valid-describe-callback, jest/valid-title, jest/no-disabled-tests
|
||||||
return enabled ? describe(name, fn) : describe.skip(name, fn);
|
return enabled ? describe(name, fn) : describe.skip(name, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user