hyperpubee
Version:
Self-publishing over the decentralised internet
499 lines (419 loc) • 16.4 kB
JavaScript
const { strict: nodeAssert } = require('assert')
const { expect } = require('chai')
const Corestore = require('corestore')
const ram = require('random-access-memory')
const utils = require('../lib/core/utils')
const { ensureIsValidPubee } = require('../lib/core/pubee-validator')
const hexlexi = require('../lib/core/hexlexi')
const {
CONTENT,
TITLE,
STRUCTURE,
ROOT,
LINE,
EMBEDDING,
EMBEDDING_LOCATION,
EMBEDDING_HASH,
EMBEDDING_PATCH,
METADATA,
LINK,
LINK_LOCATION,
LINK_HASH
} = require('../lib/core/constants')
const HyperInterface = require('hyperpubee-hyper-interface')
describe('Test pubee validator', function () {
let hyperInterface
let bee
let contentSub
let structureSub
let index0, index1, index2
let corestore
this.beforeEach(async function () {
corestore = new Corestore(ram)
await corestore.ready()
hyperInterface = new HyperInterface(corestore)
bee = await hyperInterface.createHyperbee('testBee1')
contentSub = bee.sub(CONTENT)
structureSub = bee.sub(STRUCTURE)
index0 = hexlexi.pack(0)
index1 = hexlexi.pack(1)
index2 = hexlexi.pack(2)
await addContent(index0, 'Line 1')
await bee.put(METADATA, {})
})
this.afterEach(async function () {
await corestore.close()
})
async function addToStructure (
structureKey,
keyToAdd,
structureSubspace = structureSub
) {
const { value: updatedValue } = (await structureSubspace.get(
structureKey
)) || {
value: []
}
updatedValue.push(keyToAdd)
await structureSubspace.put(structureKey, updatedValue)
}
async function addContent (index, content, structureToIncludeIn = ROOT) {
await contentSub.put(index, content)
const contentLocInBee = utils.getAbsoluteKey(CONTENT, index)
const { key: contentKey } = await bee.get(contentLocInBee)
await addToStructure(structureToIncludeIn, contentKey)
}
it('is a valid default pubee', async function () {
expect(await ensureIsValidPubee(bee)).to.equal(true)
})
it('is not valid to have no root', async function () {
await structureSub.del(ROOT)
const expectedMsg = 'A work must contain a root structure element'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('throws if any key is not a valid lexicographical int', async function () {
await addContent('z', 'z is not a valid hexadecimal lexicographic int')
const expectedMsg =
"content with invalid key: 'z' (must be lexicographic hexadecimal)"
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('throws if key is missing in order', async function () {
await addContent(index2, 'Not Okay, should have been 1')
const expectedMsg =
'Non-consecutive indexes in content: expected 1 but observed 2 (lexicographically: 02)'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('accepts if keys in correct order', async function () {
await addContent(index1, 'Okay')
await addContent(index2, 'Okay')
expect(await ensureIsValidPubee(bee)).to.equal(true)
})
it('throws if values not of json type', async function () {
bee = await hyperInterface.createHyperbee('invalidBee', {
valueEncoding: 'binary'
})
const expectedMsg =
'Invalid valueEncoding: must be json, but observed binary'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a content is the empty string', async function () {
await contentSub.put(index0, '')
const expectedMsg = `Content at index ${index0} is empty`
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a structure contains an empty array', async function () {
await structureSub.put('someStruct', [])
const expectedMsg = 'Found no entries in sub structure\x00someStruct'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a content is not part of any structure', async function () {
await contentSub.put(index1, 'Not a part of any structure')
const expectedMsg = 'The content at index \'content\x0001\' is not included in any structure'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a content is part of multiple structures', async function () {
const absIndex0Key = utils.getAbsoluteKey(CONTENT, index0)
const newStructRelKey = utils.getAbsoluteKey('anotherStruct', index0)
const newStructAbsKey = utils.getAbsoluteKey(STRUCTURE, newStructRelKey)
await addToStructure(newStructRelKey, absIndex0Key)
await addToStructure(ROOT, newStructAbsKey)
const expectedMsg = 'The content at index \'content\x0000\' is included in multiple structures: flattened indices 0 and 1'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if there is no metadata key', async function () {
await bee.del(METADATA)
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: 'A pubee must have a metadata key'
})
})
describe('Structure logic', function () {
let absIndex0
let absTitleKey
let relTitleKey
let line0RelKey
this.beforeEach(async function () {
absIndex0 = utils.getAbsoluteKey(CONTENT, index0)
absTitleKey = utils.getAbsoluteKey(STRUCTURE, TITLE, index0)
relTitleKey = utils.getAbsoluteKey(TITLE, index0)
line0RelKey = utils.getAbsoluteKey(LINE, index0)
await structureSub.del(ROOT)
await addToStructure(relTitleKey, absIndex0)
await addToStructure(ROOT, absTitleKey)
})
it('The fixture is a valid pubee', async function () {
expect(await ensureIsValidPubee(bee)).to.equal(true)
})
it('Throws if a non-root structure is not contained in any structure', async function () {
await structureSub.del(ROOT)
await addContent(
index1,
'Another line to go in the root, so it is not empty now the title is removed from it',
ROOT
)
const expectedMsg = 'The structure at index \'structure\x00title\x0000\' is not included in any structure'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a structure is contained in more than 1 structure', async function () {
const absLineKey = utils.getAbsoluteKey(STRUCTURE, line0RelKey)
await addContent(index1, 'Content 1', line0RelKey)
await addToStructure(line0RelKey, absTitleKey)
await addToStructure(ROOT, absLineKey)
const expectedMsg = 'The structure at index \'structure\x00title\x0000\' is included in multiple structures: flattened indices 1 and 2'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a structure references a non-existing key', async function () {
const keyPointingNowhere = utils.getAbsoluteKey(CONTENT, index2)
await addToStructure(ROOT, keyPointingNowhere)
const expectedMsg = 'Structure \'root\' references a non-existing key: content\x0002'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a structure is not indexed properly', async function () {
const structRelKey = utils.getAbsoluteKey(LINE, index2)
const structAbsKey = utils.getAbsoluteKey(STRUCTURE, LINE, index2)
await addContent(index1, 'some content', structRelKey)
await addToStructure(ROOT, structAbsKey)
const expectedMsg = 'Non-consecutive indexes in structure\x00line: expected 0 but observed 2 (lexicographically: 02)'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a structure has a non-regex-matching name', async function () {
const structAbsKey = utils.getAbsoluteKey(
STRUCTURE,
'án_invalid_char',
index0
)
const structRelKey = utils.getAbsoluteKey('án_invalid_char', index0)
await addToStructure(ROOT, structAbsKey)
await addContent(index1, 'some content', structRelKey)
const expectedMsg =
"Structure key must match /^\\w{1,64}$/ regex ('án_invalid_char')"
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
})
describe('Embedding logic', function () {
let refBee
let refEmbeddingSub
let refStructureSub
let embeddingSub
let beeKey
this.beforeEach(async function () {
refBee = await hyperInterface.createHyperbee('refBee')
refEmbeddingSub = refBee.sub(EMBEDDING)
refStructureSub = refBee.sub(STRUCTURE)
embeddingSub = bee.sub(EMBEDDING)
beeKey = utils.getKeyAsStr(bee.feed.key)
})
function createEmbedding ({ key, index, patch } = {}) {
const res = {}
if (key !== undefined) {
res[EMBEDDING_HASH] = key
}
if (index !== undefined) {
res[EMBEDDING_LOCATION] = index
}
if (patch !== undefined) {
res[EMBEDDING_PATCH] = patch
}
return res
}
async function addEmbedding (index, embedding) {
await refEmbeddingSub.put(index, embedding)
const locInBee = utils.getAbsoluteKey(EMBEDDING, index)
const { key: embeddingKey } = await refBee.get(locInBee)
await addToStructure(ROOT, embeddingKey, refStructureSub)
return locInBee
}
it('Throws if an embedding does not contain a key', async function () {
const invalidEmbedding = createEmbedding({ index: index0 })
await addEmbedding(index0, invalidEmbedding)
const expectedMsg = 'Embedding at index \'00\': ' +
'referencedHash must be a valid hypercore hash (undefined)'
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
it('Throws if an embedding does not contain an index', async function () {
const invalidEmbedding = createEmbedding({ key: beeKey })
await addEmbedding(index0, invalidEmbedding)
const expectedMsg = 'Embedding at index \'00\': ' +
'Invalid referenced location (undefined)'
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
it("Throws if an embedding's key is not a valid key", async function () {
const key = 'a'.repeat(65)
const invalidEmbedding = createEmbedding({
key,
index: index0
})
await addEmbedding(index0, invalidEmbedding)
const expectedMsg = 'Embedding at index \'00\': referencedHash ' +
`must be a valid hypercore hash (${key})`
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
it('Throws if an embedding is not included in any structure', async function () {
const embedding = createEmbedding({
key: beeKey,
index: index0
})
await embeddingSub.put(index0, embedding)
const expectedMsg = 'The embedding at index \'embedding\x0000\' is not included in any structure'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if an embedding is included in multiple structures', async function () {
const embedding = createEmbedding({
key: beeKey,
index: index0
})
await embeddingSub.put(index0, embedding)
const embeddingLocInBee = utils.getAbsoluteKey(EMBEDDING, index0)
const structRelKey = utils.getAbsoluteKey(LINE, index0)
const structAbsKey = utils.getAbsoluteKey(STRUCTURE, LINE, index0)
await addToStructure(structRelKey, embeddingLocInBee)
await addToStructure(ROOT, structAbsKey)
await addToStructure(ROOT, embeddingLocInBee)
const expectedMsg = 'The embedding at index \'embedding\x0000\' is included in multiple structures: flattened indices 0 and 3'
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if an embedding is not indexed correctly', async function () {
const embedding = createEmbedding({
key: beeKey,
index: index0
})
await addEmbedding(index0, embedding)
const embedding2 = createEmbedding({
key: beeKey,
index: index0
})
await addEmbedding(index2, embedding2)
const expectedMsg = 'Non-consecutive indexes in embedding: expected 1 but observed 2 (lexicographically: 02)'
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
})
describe('Link logic', function () {
let refBee
let refStructureSub
let linkSub
let validLink
this.beforeEach(async function () {
refBee = await hyperInterface.createHyperbee('refBee')
refStructureSub = refBee.sub(STRUCTURE)
linkSub = bee.sub(LINK)
validLink = createLink({
key: 'a'.repeat(64)
})
})
function createLink ({ key, location } = {}) {
const res = {}
if (key !== undefined) {
res[LINK_HASH] = key
}
if (location !== undefined) {
res[LINK_LOCATION] = location
}
return res
}
async function addLink (index, link) {
await refBee.sub(LINK).put(index, link)
const locInBee = utils.getAbsoluteKey(LINK, index)
const { key: linkKey } = await refBee.get(locInBee)
await addToStructure(ROOT, linkKey, refStructureSub)
return locInBee
}
it('Throws if a link does not contain a hash', async function () {
const invalidLink = createLink({ })
await addLink(index0, invalidLink)
const expectedMsg = 'Link at index \'00\': LinkedHash must be a valid hypercore hash (undefined)'
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
it("Throws if a link's hash is not a valid hash", async function () {
const invalidLink = createLink({
key: 'a'.repeat(65)
})
await addLink(index0, invalidLink)
const expectedMsg = 'Link at index \'00\': LinkedHash must be a valid ' +
`hypercore hash (${'a'.repeat(65)})`
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
it("Throws if a link's location is invalid", async function () {
const invalidLink = createLink({
key: 'a'.repeat(64),
location: 'NOPE'
})
await addLink(index0, invalidLink)
const expectedMsg = "Link at index '00': Invalid linked location (NOPE)"
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
it('Throws if a link is not included in any structure', async function () {
await linkSub.put(index0, validLink)
const expectedMsg = `The ${LINK} at index '${LINK}\x0000' is not included in any structure`
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a link is included in multiple structures', async function () {
await linkSub.put(index0, validLink)
const linkLocInBee = utils.getAbsoluteKey(LINK, index0)
const structRelKey = utils.getAbsoluteKey(LINE, index0)
const structAbsKey = utils.getAbsoluteKey(STRUCTURE, LINE, index0)
await addToStructure(structRelKey, linkLocInBee)
await addToStructure(ROOT, structAbsKey)
await addToStructure(ROOT, linkLocInBee)
const expectedMsg = `The ${LINK} at index '${LINK}\x0000' is included in multiple structures: flattened indices 0 and 3`
await nodeAssert.rejects(ensureIsValidPubee(bee), {
message: expectedMsg
})
})
it('Throws if a link is not indexed correctly', async function () {
await addLink(index0, validLink)
const link2 = createLink({
key: utils.getKeyAsStr(bee.feed.key)
})
await addLink(index2, link2)
const expectedMsg = `Non-consecutive indexes in ${LINK}: expected 1 but observed 2 (lexicographically: 02)`
await nodeAssert.rejects(ensureIsValidPubee(refBee), {
message: expectedMsg
})
})
})
})