diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e84fb5bf..a31c258b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,6 +49,27 @@ jobs: env: CI: true + type-test: + name: Type test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v1 + with: + node-version: 14 + + - name: Restore cache + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + + - run: yarn install + + - run: yarn test:types + publish: name: Publish runs-on: ubuntu-latest diff --git a/README.md b/README.md index 35b9d704..4fbcb67c 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ module.exports = { ### Testing -Run `yarn test` to run the unit tests. +Run `yarn test` to run the unit tests, and `yarn test:types` to run the type tests. When writing a bugfix, start by writing a test that reproduces the problem. It should fail with the current version of Lambda Wrapper, and pass once you've implemented the fix. diff --git a/package.json b/package.json index 393748a5..2f18ea06 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "clean": "rm -rf dist", "lint": "eslint src tests", "test": "jest", + "test:types": "tsc -p tsconfig-type-test.json", "coverage": "yarn test --coverage" }, "devDependencies": { @@ -35,6 +36,7 @@ "eslint": "^8.57.0", "eslint-plugin-import": "^2.25.2", "eslint-plugin-jsdoc": "^39.3.2", + "expect-type": "^0.18.0", "jest": "^29.7.0", "nyc": "^15.1.0", "semantic-release": "^19.0.5", diff --git a/tests/type/.eslintrc.yml b/tests/type/.eslintrc.yml new file mode 100644 index 00000000..20548d44 --- /dev/null +++ b/tests/type/.eslintrc.yml @@ -0,0 +1,2 @@ +rules: + '@typescript-eslint/ban-ts-comment': off diff --git a/tests/type/DependencyInjection.spec.ts b/tests/type/DependencyInjection.spec.ts new file mode 100644 index 00000000..cb2a6022 --- /dev/null +++ b/tests/type/DependencyInjection.spec.ts @@ -0,0 +1,39 @@ +import { expectTypeOf } from 'expect-type'; + +import lambdaWrapper, { + DependencyAwareClass, + DependencyInjection, + LoggerService, + SQSService, +} from '../../src/index'; +import { mockContext, mockEvent } from '../mocks/aws'; + +describe('type.DependencyInjection', () => { + const di = new DependencyInjection(lambdaWrapper.config, mockEvent, mockContext); + + describe('get', () => { + it('should return a LoggerService instance if passed LoggerService', () => { + expectTypeOf(di.get(LoggerService)).toEqualTypeOf(); + }); + + it('should return an SQSService instance if passed SQSService', () => { + // for this one we also expect the TConfig type parameter to be correct + expectTypeOf(di.get(SQSService)).toEqualTypeOf>(); + }); + + it('should accept any dependency-aware class', () => { + // Currently, the type system does not restrict you to getting only + // dependencies that are specified in the Lambda Wrapper config, however + // doing so will result in a runtime error. + class Good extends DependencyAwareClass {} + expectTypeOf(di.get).toBeCallableWith(Good); + expectTypeOf(di.get(Good)).toEqualTypeOf>(); + }); + + it('should not accept classes that are not dependency-aware', () => { + class Bad {} + // @ts-expect-error: it should _not_ be callable with Bad + expectTypeOf(di.get).toBeCallableWith(Bad); + }); + }); +}); diff --git a/tests/type/SQSService.spec.ts b/tests/type/SQSService.spec.ts new file mode 100644 index 00000000..6737a712 --- /dev/null +++ b/tests/type/SQSService.spec.ts @@ -0,0 +1,62 @@ +import { expectTypeOf } from 'expect-type'; + +import lambdaWrapper, { + DependencyInjection, + QueueName, + SQSService, +} from '../../src/index'; +import { mockContext, mockEvent } from '../mocks/aws'; + +describe('type.SQSService', () => { + const lwWithQueues = lambdaWrapper.configure({ + sqs: { + queues: { + test1: 'test-queue-1', + test2: 'test-queue-2', + }, + }, + }); + + type expectedQueueNames = 'test1' | 'test2'; + + const di = new DependencyInjection(lwWithQueues.config, mockEvent, mockContext); + const sqs = di.get(SQSService); + + describe('QueueName', () => { + it('should infer queue names from config', () => { + // use our configured LambdaWrapper instance + type queueName = QueueName; + expectTypeOf().toEqualTypeOf(); + }); + + it('should infer `never` if no queues are configured', () => { + // use the package's default out-of-the-box LambdaWrapper instance + type queueName = QueueName; + expectTypeOf().toBeNever(); + }); + }); + + describe('batchDelete', () => { + it('should accept only configured queue names', () => { + expectTypeOf(sqs.batchDelete).parameter(0).toEqualTypeOf(); + }); + }); + + describe('getMessageCount', () => { + it('should accept only configured queue names', () => { + expectTypeOf(sqs.getMessageCount).parameter(0).toEqualTypeOf(); + }); + }); + + describe('publish', () => { + it('should accept only configured queue names', () => { + expectTypeOf(sqs.publish).parameter(0).toEqualTypeOf(); + }); + }); + + describe('receive', () => { + it('should accept only configured queue names', () => { + expectTypeOf(sqs.receive).parameter(0).toEqualTypeOf(); + }); + }); +}); diff --git a/tsconfig-type-test.json b/tsconfig-type-test.json new file mode 100644 index 00000000..e0c6480f --- /dev/null +++ b/tsconfig-type-test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + }, + "include": [ + "tests/type/*.spec.ts", + "types/*.d.ts", + ] +} diff --git a/yarn.lock b/yarn.lock index b5d47379..ba840d29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2913,6 +2913,11 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== +expect-type@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.18.0.tgz#16f62ff9d04950d7d286ed9179a52bc3ea366f2b" + integrity sha512-xjKoyyDLoia2h1WF+vwV8AmEpQ0drGW0InRgyywAHyOC+XSPYMxGoMXSwPjXs46D8FgLmp32sHMd1KrVingDuQ== + expect@^29.0.0, expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc"