Skip to content

Commit

Permalink
test: add metadata cases (#12)
Browse files Browse the repository at this point in the history
* test(keys): add metadata cases

* test: improve coverage
  • Loading branch information
Kikobeats authored Apr 8, 2024
1 parent d504221 commit be2c6bb
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 54 deletions.
1 change: 0 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const createKeys = require('./keys')
const createPlans = require('./plans')

module.exports = ({ serialize = JSONB.stringify, deserialize = JSONB.parse, redis = new Map() } = {}) => {
if (!redis) throw TypeError('The argument `store` is required.')
const plans = createPlans({ serialize, deserialize, redis })
const keys = createKeys({ serialize, deserialize, redis, plans })
return { keys, plans }
Expand Down
8 changes: 5 additions & 3 deletions src/keys.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const { pick, uid, validateKey, assert } = require('./util')
const { pick, uid, validateKey, assert, assertMetadata } = require('./util')

const KEY_PREFIX = 'key_'
const KEY_FIELDS = ['name', 'description', 'enabled', 'value', 'plan']
Expand All @@ -22,6 +22,7 @@ module.exports = ({ serialize, deserialize, plans, redis } = {}) => {
*/
const create = async (opts = {}) => {
assert(typeof opts.name === 'string' && opts.name.length > 0, 'The argument `name` is required.')
opts.metadata = assertMetadata(opts.metadata)
const key = pick(opts, KEY_FIELDS.concat(KEY_FIELDS_OBJECT))
key.id = await uid({ redis, prefix: KEY_PREFIX, size: 5 })
key.createdAt = key.updatedAt = Date.now()
Expand Down Expand Up @@ -66,7 +67,8 @@ module.exports = ({ serialize, deserialize, plans, redis } = {}) => {
assert(plan === null, `The key \`${keyId}\` is associated with the plan \`${getKey.plan}\``)
}
const isDeleted = (await redis.del(getKey(keyId, { verify: true }))) === 1
return assert(isDeleted, `The key \`${keyId}\` does not exist.`) || isDeleted
assert(isDeleted, `The key \`${keyId}\` does not exist.`)
return isDeleted
}

/**
Expand All @@ -84,7 +86,7 @@ module.exports = ({ serialize, deserialize, plans, redis } = {}) => {
*/
const update = async (keyId, opts) => {
const currentKey = await retrieve(keyId, { throwError: true })
const metadata = Object.assign({}, currentKey.metadata, opts.metadata)
const metadata = Object.assign({}, currentKey.metadata, assertMetadata(opts.metadata))
const key = Object.assign(currentKey, pick(opts, KEY_FIELDS), {
updatedAt: Date.now()
})
Expand Down
3 changes: 2 additions & 1 deletion src/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ module.exports = ({ serialize, deserialize, redis } = {}) => {
*/
const del = async planId => {
const isDeleted = (await redis.del(getKey(planId, { validate: true }))) === 1
return assert(isDeleted, `The plan \`${planId}\` does not exist.`) || isDeleted
assert(isDeleted, `The plan \`${planId}\` does not exist.`)
return isDeleted
}

/**
Expand Down
124 changes: 87 additions & 37 deletions test/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const { keys, plans } = openkey({ redis: new Redis() })

test.beforeEach(async () => {
const keys = await redis.keys(`${KEY_PREFIX}*`)

if (keys.length > 0) await redis.del(keys)
})

Expand All @@ -24,6 +23,19 @@ test('.create # `name` is required', async t => {
t.is(error.name, 'TypeError')
})

test('.create # `metadata` must be a flat object', async t => {
const error = await t.throwsAsync(keys.create({ name: '[email protected]', metadata: { tier: { type: 'new' } } }))

t.is(error.message, "The metadata field 'tier' can't be an object.")
t.is(error.name, 'TypeError')
})

test('.create # `metadata` as undefined is omitted', async t => {
const key = keys.create({ name: '[email protected]', metadata: { cc: undefined } })

t.is(key.metadata, undefined)
})

test('.create # error if plan is invalid', async t => {
const error = await t.throwsAsync(keys.create({ name: '[email protected]', plan: 123 }))

Expand All @@ -48,7 +60,22 @@ test('.create', async t => {
t.true(key.enabled)
})

test('.retrieve', async t => {
test('.create # associate a plan', async t => {
const plan = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

const key = await keys.create({ name: '[email protected]', plan: plan.id })

t.true(key.id.startsWith('key_'))
t.truthy(key.createdAt)
t.is(key.createdAt, key.updatedAt)
t.is(key.value.length, 16)
t.true(key.enabled)
})

test('.retrieve # a key previously created', async t => {
const { id, value } = await keys.create({ name: '[email protected]' })

const { createdAt, updatedAt, ...key } = await keys.retrieve(id)
Expand All @@ -61,6 +88,10 @@ test('.retrieve', async t => {
})
})

test('.retrieve # a key not previously created', async t => {
t.is(await keys.retrieve('key_1'), null)
})

test('.update', async t => {
const { id, value, createdAt } = await keys.create({
name: '[email protected]'
Expand All @@ -86,47 +117,30 @@ test('.update', async t => {
t.deepEqual(await keys.retrieve(id), { ...key, updatedAt })
})

test('.update # error if plan is invalid', async t => {
const { id } = await keys.create({ name: '[email protected]' })

const error = await t.throwsAsync(
keys.update(id, {
description: 'new description',
enabled: false,
plan: 123
})
)
test('.update # error if key is invalid', async t => {
const error = await t.throwsAsync(keys.update('id', { foo: 'bar' }))
t.is(error.message, 'The id `id` must to start with `key_`.')
t.is(error.name, 'TypeError')
})

t.is(error.message, 'The id `123` must to start with `plan_`.')
test('.update # error if key does not exist', async t => {
const error = await t.throwsAsync(keys.update('key_id'))
t.is(error.message, 'The key `key_id` does not exist.')
t.is(error.name, 'TypeError')
})

test('.update # error if plan does not exist', async t => {
test('.update # error if plan is invalid', async t => {
const { id } = await keys.create({ name: '[email protected]' })

const error = await t.throwsAsync(
keys.update(id, {
description: 'new description',
enabled: false,
plan: 'plan_123'
})
)

t.is(error.message, 'The plan `plan_123` does not exist.')
const error = await t.throwsAsync(keys.update(id, { plan: 'id' }))
t.is(error.message, 'The id `id` must to start with `plan_`.')
t.is(error.name, 'TypeError')
})

test('.update # error if key does not exist', async t => {
{
const error = await t.throwsAsync(keys.update('id', { foo: 'bar' }))
t.is(error.message, 'The id `id` must to start with `key_`.')
t.is(error.name, 'TypeError')
}
{
const error = await t.throwsAsync(keys.update('key_id', { foo: 'bar' }))
t.is(error.message, 'The key `key_id` does not exist.')
t.is(error.name, 'TypeError')
}
test('.update # error if plan does not exist', async t => {
const { id } = await keys.create({ name: '[email protected]' })
const error = await t.throwsAsync(keys.update(id, { plan: 'plan_id' }))
t.is(error.message, 'The plan `plan_id` does not exist.')
t.is(error.name, 'TypeError')
})

test('.update # add a plan', async t => {
Expand All @@ -142,10 +156,39 @@ test('.update # add a plan', async t => {
})

test('.update # add metadata', async t => {
{
const { id } = await keys.create({ name: '[email protected]' })
const key = await keys.update(id, { metadata: { cc: '[email protected]' } })
t.is(key.metadata.cc, '[email protected]')
}
{
const { id } = await keys.create({ name: '[email protected]' })
await keys.update(id, { metadata: { cc: '[email protected]' } })
const key = await keys.update(id, { metadata: { cc: '[email protected]', version: 2 } })

t.is(key.metadata.cc, '[email protected]')
t.is(key.metadata.version, 2)
}
})

test('.update # metadata must be a flat object', async t => {
const { id } = await keys.create({ name: '[email protected]' })
const key = await keys.update(id, { metadata: { cc: '[email protected]' } })
const error = await t.throwsAsync(keys.update(id, { metadata: { email: { cc: '[email protected]' } } }))
t.is(error.message, "The metadata field 'email' can't be an object.")
t.is(error.name, 'TypeError')
})

t.is(key.metadata.cc, '[email protected]')
test('.update # metadata as undefined is omitted', async t => {
{
const { id } = await keys.create({ name: '[email protected]' })
const key = await keys.update(id, { metadata: { email: undefined } })
t.is(key.metadata, undefined)
}
{
const { id } = await keys.create({ name: '[email protected]' })
const key = await keys.update(id, { metadata: { cc: '[email protected]', bcc: undefined } })
t.deepEqual(Object.keys(key.metadata), ['cc'])
}
})

test('.update # prevent to add random data', async t => {
Expand All @@ -155,6 +198,13 @@ test('.update # prevent to add random data', async t => {
t.is(key.foo, undefined)
})

test('.update # prevent to modify the key id', async t => {
const { id } = await keys.create({ name: '[email protected]' })
const key = await keys.update(id, { id: 'foo' })

t.is(key.id, id)
})

test.serial('.list', async t => {
const { id: id1 } = await keys.create({ name: '[email protected]' })
const { id: id2 } = await keys.create({ name: '[email protected]' })
Expand Down
24 changes: 12 additions & 12 deletions test/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ test('.create', async t => {
t.deepEqual(plan.throttle, { burstLimit: 1000, rateLimit: 10 })
})

test('.retrieve # a plan not previosuly declared', async t => {
test('.retrieve # a plan not previously created', async t => {
t.is(await plans.retrieve('plan_1'), null)
})

test('.retrieve # a plan previosuly declared', async t => {
test('.retrieve # a plan previously created', async t => {
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
Expand Down Expand Up @@ -232,17 +232,16 @@ test('.update # prevent to modify the plan id', async t => {
t.is(plan.id, id)
})

test('.update # error if plan is invalid', async t => {
const error = await t.throwsAsync(plans.update('id', { foo: 'bar' }))
t.is(error.message, 'The id `id` must to start with `plan_`.')
t.is(error.name, 'TypeError')
})

test('.update # error if plan does not exist', async t => {
{
const error = await t.throwsAsync(plans.update('id', { foo: 'bar' }))
t.is(error.message, 'The id `id` must to start with `plan_`.')
t.is(error.name, 'TypeError')
}
{
const error = await t.throwsAsync(plans.update('plan_id', { foo: 'bar' }))
t.is(error.message, 'The plan `plan_id` does not exist.')
t.is(error.name, 'TypeError')
}
const error = await t.throwsAsync(plans.update('plan_id', { foo: 'bar' }))
t.is(error.message, 'The plan `plan_id` does not exist.')
t.is(error.name, 'TypeError')
})

test.serial('.list', async t => {
Expand Down Expand Up @@ -277,6 +276,7 @@ test('.del', async t => {

test('.del # error if plan does not exist', async t => {
const error = await t.throwsAsync(plans.del('plan_id'))

t.is(error.message, 'The plan `plan_id` does not exist.')
t.is(error.name, 'TypeError')
})

0 comments on commit be2c6bb

Please sign in to comment.