Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,30 @@ jobs:
run: pnpm install
- name: Run smoke tests
run: pnpm test:smoke
unit-test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Setup PNPM 7
uses: pnpm/action-setup@v2.0.1
with:
version: latest
- name: Setup Node 18
uses: actions/setup-node@v2
with:
node-version: 18
registry-url: 'https://registry.npmjs.org/'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run unit tests
run: pnpm test:unit
publish-docker-image:
name: Publish
runs-on: ubuntu-latest
needs: [lint, smoke-test]
needs: [lint, smoke-test, unit-test]
if: github.ref == 'refs/head/trunk'
steps:
- name: Checkout the repository
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"develop": "tsx watch --clear-screen ./source/server.ts",
"test:lint": "xo",
"test:smoke": "ava --match smoke*",
"test:unit": "ava --match unit*",
"test": "run-s test:*",
"patch": "patch-package",
"prepare": "run-s patch compile"
Expand Down Expand Up @@ -78,6 +79,7 @@
"nodeArguments": [
"--loader=tsx",
"--no-warnings"
]
],
"concurrency": 1
}
}
8 changes: 8 additions & 0 deletions test/fixtures/invalid-identifier-properties-email.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
Comment thread
gamemaker1 marked this conversation as resolved.
"identifier": {
"type": "email",
"properties": {
"email": 0
}
}
}
6 changes: 6 additions & 0 deletions test/fixtures/invalid-identifier-properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"identifier": {
"type": "email",
"properties": "not an object"
}
}
3 changes: 3 additions & 0 deletions test/fixtures/invalid-identifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"identifier": "notobject"
}
8 changes: 8 additions & 0 deletions test/fixtures/invalid-json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"identifier": {
"type"= "email",
"properties": {
"email"="techmantejas@gmail.com"
}
}
}
Empty file added test/fixtures/no-body.json
Empty file.
6 changes: 6 additions & 0 deletions test/fixtures/no-external-identifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"identifier": {
"type": "email",
"properties": {}
}
}
1 change: 1 addition & 0 deletions test/fixtures/no-identifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions test/fixtures/no-properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"identifier": {
"type": "email"
}
}
3 changes: 3 additions & 0 deletions test/fixtures/no-type.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"identifier": {}
}
221 changes: 221 additions & 0 deletions test/suites/unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// tests/suites/unit.ts
// The file contains the unit test suite.

import type { FastifyInstance } from 'fastify'
import type { TestFn } from 'ava'

// eslint-disable-next-line ava/use-test
import ava from 'ava'

import { fixture } from '../helpers/fixtures.js'

import { build } from '../../source/loaders/index.js'
import { ServerError } from '../../source/utilities/errors.js'

type ServerContext = {
server: FastifyInstance
}

const test = ava as TestFn<ServerContext>
const json = JSON

// Create the server before running the tests.
test.before(async (t) => {
t.context.server = build({ disableRequestLogging: true })
})

test('unit | post /identity | 400 improper-payload [no body]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
// Json.stringify() is used over here to ensure that the empty file is considered as a valid JSON
payload: json.stringify(fixture('no-body')),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the body must be object type.
t.regex(error?.message, /body must be object/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [no identifier]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('no-identifier'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the no identifier type.
t.regex(error?.message, /identifier/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [invalid identifier]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('invalid-identifier'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the invalid identifier type.
t.regex(error?.message, /identifier/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [no type]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('no-type'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the no type type.
t.regex(error?.message, /type/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [no properties]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('no-properties'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the no properties type.
t.regex(error?.message, /properties/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [invalid identifier properties]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('invalid-identifier-properties'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the invalid identifier properties type.
t.regex(error?.message, /identifier\/properties must be object/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [no external identifier]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('no-external-identifier'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the no external identifier type.
t.regex(
error?.message,
/The properties for the external identifier were missing or incomplete/,
)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 400 improper-payload [invalid identifier properties email]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('invalid-identifier-properties-email'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('improper-payload')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the invalid identifier properties email type.
t.regex(
error?.message,
/The properties for the external identifier were missing or incomplete/,
)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})

test('unit | post /identity | 500 server-crash [invalid json]', async (t) => {
const response = await t.context.server.inject({
method: 'post',
url: '/identity',
payload: fixture('invalid-json'),
headers: { 'content-type': 'application/json' },
})

const { meta, error, data } = json.parse(response.payload)
const expectedError = new ServerError('server-crash')

// Check that the request failed with the expected HTTP status code and
// error code.
t.is(meta?.status, expectedError.status)
t.is(error?.code, expectedError.code)
// Check that the message is related to the invalid identifier properties email type.
t.regex(error?.message, /An unexpected error occurred/)
// Check that only the `error` and `meta` fields were returned.
t.is(data, undefined)
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the test cases you have added so far are great!

Just a few more that you could add:

  • The ID of the DID returned when a valid request is passed is the first 16 letters of the hash of the email ID. Could you add a test case to make sure that is the case everytime?
  • If you try to make a new DID with the same email address, the expected behaviour is for it to return a DID with the same ID, but with different verification and assertion methods. Could you add a test case for this as well?