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:
Joachim Van Herwegen 2022-01-25 11:44:24 +01:00
commit 90a6460c8d
39 changed files with 1832 additions and 1808 deletions

View File

@ -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' ],

View File

@ -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. -->

View File

@ -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:

View File

@ -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

View File

@ -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).

View File

@ -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",

View File

@ -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",

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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.');
} }
} }

View File

@ -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.');
} }
} }

View File

@ -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 }};
} }

View File

@ -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,

View File

@ -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);
}); });
}); });
}); });

View File

@ -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>.' ],

View File

@ -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',

View File

@ -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',

View File

@ -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' }});
});
}); });

View File

@ -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();
}); });
}); });

View File

@ -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();
}); });

View File

@ -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');

View File

@ -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> => {

View File

@ -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);
}); });

View File

@ -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!',

View File

@ -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');
}); });
}); });
}); });

View File

@ -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(
`[ `[
{ {

View File

@ -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> => {

View File

@ -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.',

View File

@ -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();
}); });

View File

@ -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);
}); });
}); });
}); });

View File

@ -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> => {

View File

@ -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.');
}); });
}); });

View File

@ -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> => {

View File

@ -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);
}); });

View File

@ -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';

View File

@ -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);

View File

@ -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);

View File

@ -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);
} }