Skip to content

Commit

Permalink
test: add metadata cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Kikobeats committed Apr 7, 2024
1 parent 8fe1718 commit 67ce48f
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 8 deletions.
7 changes: 4 additions & 3 deletions src/plans.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 PLAN_PREFIX = 'plan_'
const PLAN_QUOTA_PERIODS = ['day', 'week', 'month']
Expand All @@ -22,7 +22,7 @@ module.exports = ({ serialize, deserialize, redis } = {}) => {
* @param {number} [options.throttle.rateLimit] - The rate limit of the plan.
* @param {Object} [options.metadata] - Any extra information can be attached here.
*
* @returns {Object|null} The plan object, null if it doesn't exist.
* @returns {Object} The plan object.
*/
const create = async (opts = {}) => {
assert(typeof opts.name === 'string' && opts.name.length > 0, 'The argument `name` is required.')
Expand All @@ -31,6 +31,7 @@ module.exports = ({ serialize, deserialize, redis } = {}) => {
`The argument \`quota.period\` must be ${PLAN_QUOTA_PERIODS.map(period => `\`${period}\``).join(' or ')}.`
)
assert(opts.quota.limit > 0, 'The argument `quota.limit` must be a positive number.')
opts.metadata = assertMetadata(opts.metadata)
const plan = pick(opts, PLAN_FIELDS.concat(PLAN_FIELDS_OBJECT))
plan.id = await uid({ redis, prefix: PLAN_PREFIX, size: 5 })
plan.createdAt = plan.updatedAt = Date.now()
Expand Down Expand Up @@ -88,7 +89,7 @@ module.exports = ({ serialize, deserialize, redis } = {}) => {
const update = async (planId, opts) => {
const currentPlan = await retrieve(planId, { throwError: true })
const quota = Object.assign(currentPlan.quota, opts.quota)
const metadata = Object.assign({}, currentPlan.metadata, opts.metadata)
const metadata = Object.assign({}, currentPlan.metadata, opts.assertMetadata(opts.metadata))
const plan = Object.assign(currentPlan, pick(opts, PLAN_FIELDS), {
quota,
updatedAt: Date.now()
Expand Down
25 changes: 23 additions & 2 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,30 @@ const validateKey =
return id
}

const assertMetadata = metadata => {
if (metadata) {
assert(isPlainObject(metadata), 'The metadata must be a flat object.')
Object.keys(metadata).forEach(key => {
assert(!isPlainObject(metadata[key]), `The metadata field '${key}' can't be an object.`)
if (metadata[key] === undefined) delete metadata[key]
})
return Object.keys(metadata).length ? metadata : undefined
}
}

const isPlainObject = value => {
if (!value || typeof value !== 'object' || value.toString() !== '[object Object]') {
return false
}

const prototype = Object.getPrototypeOf(value)
return prototype === null || prototype === Object.prototype
}

module.exports = {
uid,
pick,
assert,
assertMetadata,
pick,
uid,
validateKey
}
112 changes: 109 additions & 3 deletions test/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,51 @@ test('.create # `quota` is required', async t => {
}
})

test('.create # `metadata` must be a flat object', async t => {
{
const error = await t.throwsAsync(
plans.create({
name: 'free tier',
quota: { period: 'week', limit: 1000 },
metadata: { tier: { type: 'new' } }
})
)
t.is(error.message, "The metadata field 'tier' can't be an object.")
t.is(error.name, 'TypeError')
}
{
const error = await t.throwsAsync(
plans.create({
name: 'free tier',
quota: { period: 'week', limit: 1000 },
metadata: 'foo'
})
)
t.is(error.message, 'The metadata must be a flat object.')
t.is(error.name, 'TypeError')
}
})

test('.create # `metadata` as undefined is omitted', async t => {
{
const plan = await plans.create({
name: 'free tier',
quota: { period: 'week', limit: 1000 },
metadata: { tier: undefined }
})
t.is(plan.metadata, undefined)
}
{
const plan = await plans.create({
name: 'free tier',
quota: { period: 'week', limit: 1000 },
metadata: { tier: 'free', version: undefined }
})

t.deepEqual(Object.keys(plan.metadata), ['tier'])
}
})

test('.create', async t => {
const plan = await plans.create({
name: 'free tier',
Expand All @@ -68,7 +113,11 @@ test('.create', async t => {
t.deepEqual(plan.throttle, { burstLimit: 1000, rateLimit: 10 })
})

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

test('.retrieve # a plan previosuly declared', async t => {
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
Expand Down Expand Up @@ -108,13 +157,59 @@ test('.update', async t => {
})

test('.update # add metadata', async t => {
{
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

const plan = await plans.update(id, { metadata: { tier: 'free' } })
t.is(plan.metadata.tier, 'free')
}
{
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

await plans.update(id, { metadata: { tier: 'free' } })
const plan = await plans.update(id, { metadata: { tier: 'free', version: 2 } })
t.is(plan.metadata.tier, 'free')
t.is(plan.metadata.version, 2)
}
})

test('.update # metadata must be a flat object', async t => {
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

const plan = await plans.update(id, { metadata: { tier: 'free' } })
t.is(plan.metadata.tier, 'free')
const error = await t.throwsAsync(plans.update(id, { metadata: { tier: { type: 'new' } } }))
t.is(error.message, "The metadata field 'tier' can't be an object.")
t.is(error.name, 'TypeError')
})

test('.update # metadata as undefined is omitted', async t => {
{
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

const plan = await plans.update(id, { metadata: { tier: undefined } })
t.is(plan.metadata, undefined)
}

{
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

const plan = await plans.update(id, { metadata: { tier: 'free', version: undefined } })
t.deepEqual(Object.keys(plan.metadata), ['tier'])
}
})

test('.update # prevent to add random data', async t => {
Expand All @@ -126,6 +221,17 @@ test('.update # prevent to add random data', async t => {
t.is(plan.foo, undefined)
})

test('.update # prevent to modify the plan id', async t => {
const { id } = await plans.create({
name: 'free tier',
quota: { limit: 3000, period: 'day' }
})

const plan = await plans.update(id, { id: 'foo' })

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

test('.update # error if plan does not exist', async t => {
{
const error = await t.throwsAsync(plans.update('id', { foo: 'bar' }))
Expand Down

0 comments on commit 67ce48f

Please sign in to comment.