polen
Version:
A framework for delightful GraphQL developer portals
249 lines (212 loc) • 7.66 kB
text/typescript
import * as fc from 'fast-check'
import { describe, expect, test } from 'vitest'
import { Mask } from './$.js'
describe('Mask.create', () => {
test('boolean options create binary masks', () => {
const showMask = Mask.create(true)
const hideMask = Mask.create(false)
expect(showMask.type).toBe('binary')
expect(hideMask.type).toBe('binary')
if (showMask.type === 'binary' && hideMask.type === 'binary') {
expect(showMask.show).toBe(true)
expect(hideMask.show).toBe(false)
}
})
test('array options create allow mode properties mask', () => {
const mask = Mask.create(['name', 'age'])
expect(mask.type).toBe('properties')
if (mask.type === 'properties') {
expect(mask.mode).toBe('allow')
expect(mask.properties).toEqual(['name', 'age'])
}
})
test('object options create mode based on values', () => {
const allowMask = Mask.create({ name: true, age: true, password: false })
const denyMask = Mask.create({ password: false, secret: false })
expect(allowMask.type).toBe('properties')
expect(denyMask.type).toBe('properties')
if (allowMask.type === 'properties' && denyMask.type === 'properties') {
expect(allowMask.mode).toBe('allow')
expect(allowMask.properties).toEqual(['name', 'age'])
expect(denyMask.mode).toBe('deny')
expect(denyMask.properties).toEqual(['password', 'secret'])
}
})
})
describe('Mask.apply', () => {
test('binary masks show/hide data', () => {
const data = { a: 1 }
expect(Mask.apply(data, Mask.create(true))).toBe(data)
expect(Mask.apply(data, Mask.create(false))).toBe(undefined)
})
test('properties masks filter objects', () => {
const data = { name: 'John', age: 30, password: 'secret' }
// Allow mode
const allowMask = Mask.create(['name', 'age'])
expect(Mask.apply(data, allowMask)).toEqual({ name: 'John', age: 30 })
// Deny mode
const denyMask = Mask.create({ password: false })
expect(Mask.apply(data, denyMask)).toEqual({ name: 'John', age: 30 })
})
test('properties masks throw for non-objects', () => {
const mask = Mask.create(['name'])
expect(() => Mask.apply('string' as any, mask)).toThrow()
expect(() => Mask.apply(123 as any, mask)).toThrow()
expect(() => Mask.apply(null as any, mask)).toThrow()
})
})
describe('apply variants', () => {
test('applyPartial allows missing properties', () => {
const mask = Mask.create<{ name: string; age: number }>(['name', 'age'])
const partial = { name: 'John' }
expect(Mask.applyPartial(partial, mask)).toEqual({ name: 'John' })
expect(Mask.applyPartial({}, mask)).toEqual({})
})
test('applyExact works with any data for binary masks', () => {
const showMask = Mask.create(true)
const hideMask = Mask.create(false)
expect(Mask.applyExact('hello' as any, showMask)).toBe('hello')
expect(Mask.applyExact('hello' as any, hideMask)).toBe(undefined)
})
})
describe('property-based tests', () => {
test('binary masks - invariants', () => {
fc.assert(
fc.property(fc.anything(), (data) => {
expect(Mask.apply(data, Mask.create(true))).toBe(data)
expect(Mask.apply(data, Mask.create(false))).toBe(undefined)
}),
)
})
test('properties mask - allow mode filters correctly', () => {
fc.assert(
fc.property(
fc.dictionary(fc.string(), fc.anything()),
fc.array(fc.string(), { minLength: 1 }),
(obj, keys) => {
const mask = Mask.create(keys)
const result = Mask.apply(obj as any, mask)
const resultKeys = Object.keys(result as any)
// Result contains only keys that were in both mask and object
expect(resultKeys.every(key => keys.includes(key))).toBe(true)
// All requested keys that exist in obj are in result
keys.forEach(key => {
if (key in obj) {
expect(result).toHaveProperty(key, (obj as any)[key])
}
})
},
),
)
})
test('properties mask - deny mode filters correctly', () => {
fc.assert(
fc.property(
fc.dictionary(fc.string(), fc.anything()),
fc.uniqueArray(fc.string(), { minLength: 1 }),
(data, keysToRemove) => {
const maskSpec = Object.fromEntries(keysToRemove.map(k => [k, false]))
const mask = Mask.create(maskSpec)
expect(mask.type).toBe('properties')
if (mask.type !== 'properties') return
expect(mask.mode).toBe('deny')
const result = Mask.apply(data as any, mask)
// Result should not have any of the denied keys
keysToRemove.forEach(key => {
expect(Object.prototype.hasOwnProperty.call(result, key)).toBe(false)
})
// Result should have all other keys from data
Object.keys(data).forEach(key => {
if (!keysToRemove.includes(key)) {
expect(Object.prototype.hasOwnProperty.call(result, key)).toBe(true)
expect((result as any)[key]).toBe((data as any)[key])
}
})
},
),
)
})
test('undefined values are preserved', () => {
fc.assert(
fc.property(
fc.record({
a: fc.oneof(fc.anything(), fc.constant(undefined)),
b: fc.oneof(fc.anything(), fc.constant(undefined)),
c: fc.oneof(fc.anything(), fc.constant(undefined)),
}),
fc.shuffledSubarray(['a', 'b', 'c'], { minLength: 1 }),
(obj, keys) => {
const result = Mask.apply(obj as any, Mask.create(keys))
keys.forEach(key => {
if (key in obj) {
expect(result).toHaveProperty(key)
expect((result as any)[key]).toBe((obj as any)[key])
}
})
},
),
)
})
test('apply and applyPartial are consistent for complete data', () => {
fc.assert(
fc.property(
fc.dictionary(fc.string(), fc.anything()),
fc.array(fc.string(), { minLength: 1 }),
(data, keys) => {
const mask = Mask.create(keys)
expect(Mask.apply(data as any, mask)).toEqual(Mask.applyPartial(data as any, mask))
},
),
)
})
test('non-objects throw with properties masks', () => {
fc.assert(
fc.property(
fc.oneof(
fc.string(),
fc.integer(),
fc.boolean(),
fc.constant(null),
fc.constant(undefined),
),
fc.array(fc.string(), { minLength: 1 }),
(nonObject, keys) => {
const mask = Mask.create(keys)
expect(() => Mask.apply(nonObject as any, mask)).toThrow('Cannot apply properties mask to non-object data')
},
),
)
})
test('immutability invariants', () => {
fc.assert(
fc.property(
fc.dictionary(
fc.string(),
fc.oneof(
fc.string(),
fc.integer(),
fc.boolean(),
fc.constant(null),
),
),
fc.oneof(
fc.boolean(),
fc.array(fc.string()),
fc.dictionary(fc.string(), fc.boolean()),
),
(data, maskOptions) => {
const mask = Mask.create(maskOptions as any)
const originalData = { ...data }
const originalMask = JSON.parse(JSON.stringify(mask))
try {
Mask.apply(data as any, mask)
} catch {
// Ignore errors for invalid combinations
}
expect(data).toEqual(originalData)
expect(mask).toEqual(originalMask)
},
),
)
})
})