UNPKG

hyperpubee

Version:

Self-publishing over the decentralised internet

499 lines (419 loc) 16.4 kB
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 }) }) }) })