Skip to content

Commit

Permalink
Add generic type guard library to validate types in runtime (#26)
Browse files Browse the repository at this point in the history
* Add generic type guard library to validate types in runtime

* Add types to SNS plugin

* Temporarily build type-guard first

* Bump version

* Remove lock from versioning

* Use npm i
  • Loading branch information
abdala authored Nov 18, 2024
1 parent a7f28d9 commit 6ab18f6
Show file tree
Hide file tree
Showing 40 changed files with 441 additions and 3,130 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ jobs:
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm ci
- run: npm i
- run: npm run build
- run: npm publish --workspaces --access public
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm i
- run: |
npx playwright install
npx playwright install-deps chromium
- run: npm run build -w type-guard
- run: npm run build
- run: npm test
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist/
node_modules/
test-results
test-results
package-lock.json
2 changes: 1 addition & 1 deletion assert/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cgauge/assert",
"version": "0.9.0",
"version": "0.10.0",
"description": "Extra assert library",
"type": "module",
"repository": {
Expand Down
9 changes: 5 additions & 4 deletions dtc-aws-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cgauge/dtc-aws-plugin",
"version": "0.9.0",
"version": "0.10.0",
"description": "AWS plugin for Declarative TestCases",
"repository": {
"type": "git",
Expand All @@ -21,9 +21,10 @@
"@aws-sdk/client-sns": "^3.645.0",
"@aws-sdk/lib-dynamodb": "^3.645.0",
"@aws-sdk/util-dynamodb": "^3.645.0",
"@cgauge/assert": "^0.9.0",
"@cgauge/dtc": "^0.9.0",
"@cgauge/nock-aws": "^0.9.0"
"@cgauge/assert": "^0.10.0",
"@cgauge/dtc": "^0.10.0",
"@cgauge/nock-aws": "^0.10.0",
"@cgauge/type-guard": "^0.10.0"
},
"scripts": {
"build": "rm -rf dist && tsc",
Expand Down
56 changes: 29 additions & 27 deletions dtc-aws-plugin/src/dynamo-db-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import {isRecord, debug} from '@cgauge/dtc'
import {debug, info} from '@cgauge/dtc'
import {DynamoDB, AttributeValue} from '@aws-sdk/client-dynamodb'
import {DynamoDBDocument} from '@aws-sdk/lib-dynamodb'
import extraAssert from '@cgauge/assert'
import nodeAssert from 'node:assert'
import {is, optional, unknown, record, diff, TypeFromSchema, union} from 'type-assurance'

type DynamoArrange = {
table: string
item: Record<string, unknown>
const DynamoArrange = {
table: String,
item: record(String, unknown),
}
type DynamoArrange = TypeFromSchema<typeof DynamoArrange>

type DynamoAct = DynamoArrange
const DynamoAct = DynamoArrange
type DynamoAct = TypeFromSchema<typeof DynamoAct>

type DynamoAssert = {
table: string
key: Record<string, unknown>
item: Record<string, unknown>
const DynamoAssert = {
table: String,
key: record(String, unknown),
item: record(String, unknown),
}
type DynamoAssert = TypeFromSchema<typeof DynamoAssert>

export type CleanDynamoDBDelete = {
table: string
key: Record<string, unknown>
const CleanDynamoDBDelete = {
table: String,
key: record(String, unknown),
}

export type CleanDynamoDBQuery = {
table: string
index?: string
query: Record<string, unknown>
keys: string[]
const CleanDynamoDBQuery = {
table: String,
index: optional(String),
query: record(String, unknown),
keys: [String],
}

export type DynamoClean = CleanDynamoDBDelete | CleanDynamoDBQuery
const DynamoClean = union(CleanDynamoDBDelete, CleanDynamoDBQuery)
type DynamoClean = TypeFromSchema<typeof DynamoClean>

const documentClient = DynamoDBDocument.from(new DynamoDB({}))

const isDynamoAct = (v: unknown): v is DynamoAct => isRecord(v) && 'table' in v && 'item' in v
const isDynamoArrange = (v: unknown): v is {dynamodb: DynamoArrange[]} => isRecord(v) && 'dynamodb' in v
const isDynamoAssert = (v: unknown): v is {dynamodb: DynamoAssert[]} => isRecord(v) && 'dynamodb' in v
const isDynamoClean = (v: unknown): v is {dynamodb: DynamoClean[]} => isRecord(v) && 'dynamodb' in v

const executeDynamoStatement = async (statement: DynamoArrange) => {
debug(` [Arrange] Table: ${statement.table}\n`)
debug(` [Arrange] Item: ${JSON.stringify(statement.item)}\n`)
Expand Down Expand Up @@ -106,31 +106,33 @@ const cleanDynamoItems = async (clean: DynamoClean) => {
}

export const arrange = async (args: unknown) => {
if (!isDynamoArrange(args) || !Array.isArray(args.dynamodb)) {
if (!is(args, {dynamodb: [DynamoArrange]})) {
return
}

await Promise.all(args.dynamodb.map(executeDynamoStatement))
}

export const act = async (args: unknown) => {
if (!isDynamoAct(args)) {
if (!is(args, DynamoAct)) {
const mismatch = diff(args, DynamoAct)
info(`DynamoDB plugin declared but test declaration didn't match the act. Invalid ${mismatch[0]}\n`)
return
}

await documentClient.put({TableName: args.table, Item: args.item})
}

export const assert = async (args: unknown) => {
if (!isDynamoAssert(args) || !Array.isArray(args.dynamodb)) {
if (!is(args, {dynamodb: [DynamoAssert]})) {
return
}

await Promise.all(args.dynamodb.map(assertExists))
}

export const clean = async (args: unknown) => {
if (!isDynamoClean(args) || !Array.isArray(args.dynamodb)) {
if (!is(args, {dynamodb: [DynamoClean]})) {
return
}

Expand Down
48 changes: 24 additions & 24 deletions dtc-aws-plugin/src/event-bridge-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import {isRecord} from '@cgauge/dtc'
import {EventBridge} from '@aws-sdk/client-eventbridge'
import {info} from '@cgauge/dtc'
import {is, unknown, record, diff} from 'type-assurance'

type EventBridgeCall = {
eventBus: string
source: string
eventType: string
event: Record<string, unknown>
const EventBridgeAct = {
eventBus: String,
source: String,
eventType: String,
event: record(String, unknown),
}

const isEventBridgeAct = (v: unknown): v is EventBridgeCall =>
isRecord(v) && 'eventBus' in v && 'source' in v && 'eventType' in v && 'event' in v

const eventBridge = new EventBridge({})

export const act = async (args: unknown) => {
if (!isEventBridgeAct(args)) {
return
}

await eventBridge.putEvents({
Entries: [
{
Time: new Date(),
EventBusName: args.eventBus,
Source: args.source,
DetailType: args.eventType,
Detail: JSON.stringify(args.event),
},
],
})
if (!is(args, EventBridgeAct)) {
const mismatch = diff(args, EventBridgeAct)
info(`EventBridge plugin declared but test declaration didn't match the act. Invalid ${mismatch[0]}\n`)
return
}

await eventBridge.putEvents({
Entries: [
{
Time: new Date(),
EventBusName: args.eventBus,
Source: args.source,
DetailType: args.eventType,
Detail: JSON.stringify(args.event),
},
],
})
}
37 changes: 13 additions & 24 deletions dtc-aws-plugin/src/lambda-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {isRecord} from '@cgauge/dtc'
import {Lambda} from '@aws-sdk/client-lambda'
import extraAssert from '@cgauge/assert'
import {info} from '@cgauge/dtc'
import {is, unknown, record, diff} from 'type-assurance'

type LambdaCall = {
functionName: string
payload: Record<string, unknown>
const LambdaCall = {
functionName: String,
payload: record(String, unknown),
}

const isLambdaAct = (v: unknown): v is LambdaCall => isRecord(v) && 'functionName' in v && 'payload' in v

const lambda = new Lambda({})

export const invokeLambda = async (functionName: string, event: unknown): Promise<any> => {
Expand All @@ -22,46 +21,36 @@ export const invokeLambda = async (functionName: string, event: unknown): Promis
return JSON.parse(payload)
}

let response: unknown | undefined
let response: any

export const arrange = async (args: unknown) => {
if (!isRecord(args) || !('lambda' in args)) {
return
}

if (!Array.isArray(args.lambda)) {
if (!is(args, {lambda: [LambdaCall]})) {
return
}

await Promise.all(args.lambda.map((v) => invokeLambda(v.functionName, v.payload)))
}

export const act = async (args: unknown) => {
if (!isLambdaAct(args)) {
if (!is(args, LambdaCall)) {
const mismatch = diff(args, LambdaCall)
info(`Lambda plugin declared but test declaration didn't match the act. Invalid ${mismatch[0]}\n`)
return
}

response = await invokeLambda(args.functionName, args.payload)
}

export const assert = async (args: unknown) => {
if (!isRecord(args) || !('lambda' in args)) {
return
}

if (!isRecord(args.lambda)) {
if (!is(args, {lambda: LambdaCall})) {
return
}

extraAssert.objectContains(args.lambda, response as Record<string, unknown>)
extraAssert.objectContains(args.lambda, response)
}

export const clean = async (args: unknown) => {
if (!isRecord(args) || !('lambda' in args)) {
return
}

if (!Array.isArray(args.lambda)) {
if (!is(args, {lambda: [LambdaCall]})) {
return
}

Expand Down
22 changes: 13 additions & 9 deletions dtc-aws-plugin/src/sns-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import {isRecord} from '@cgauge/dtc'
import {MessageAttributeValue, SNS} from '@aws-sdk/client-sns'
import {SNS} from '@aws-sdk/client-sns'
import { info } from '@cgauge/dtc'
import {is, unknown, record, optional, union, diff} from 'type-assurance'

type SnsCall = {
topic: string
message: Record<string, unknown>
messageAttributes?: Record<string, MessageAttributeValue>
const SnsCall = {
topic: String,
message: record(String, unknown),
messageAttributes: optional(record(String, {
DataType: union(String, undefined),
StringValue: optional(String),
}))
}

const isSnsAct = (v: unknown): v is SnsCall => isRecord(v) && 'topic' in v && 'message' in v

const sns = new SNS({})

export const act = async (args: unknown) => {
if (!isSnsAct(args)) {
if (!is(args, SnsCall)) {
const mismatch = diff(args, SnsCall)
info(`SNS plugin declared but test declaration didn't match the act. Invalid ${mismatch[0]}\n`)
return
}

Expand Down
8 changes: 8 additions & 0 deletions dtc-aws-plugin/test/dynamo-db-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ nock.disableNetConnect()

afterEach(() => {network.checkForPendingMocks()})

test('It does not arrange if type does not match', () => arrange({}))

test('It does not act if type does not match', () => act({}))

test('It does not assert if type does not match', () => assert({}))

test('It does not clean if type does not match', () => clean({}))

test('It executes put item during arrange', async () => {
const item = {table: 'table', item: {a: 'b'}}

Expand Down
7 changes: 4 additions & 3 deletions dtc-graphql-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cgauge/dtc-graphql-plugin",
"version": "0.9.0",
"version": "0.10.0",
"description": "GraphQl plugin for Declarative TestCases",
"repository": {
"type": "git",
Expand All @@ -15,8 +15,9 @@
"author": "Salam Suleymanov",
"license": "LGPL-3.0-or-later",
"dependencies": {
"@cgauge/assert": "^0.9.0",
"@cgauge/dtc": "^0.9.0",
"@cgauge/assert": "^0.10.0",
"@cgauge/dtc": "^0.10.0",
"@cgauge/type-guard": "^0.10.0",
"graphql-request": "^7.1.2"
},
"scripts": {
Expand Down
Loading

0 comments on commit 6ab18f6

Please sign in to comment.