UNPKG

@coedl/ocfl

Version:

Oxford Common File Layout JS library

1,086 lines (957 loc) 38 kB
const path = require("path"); const { remove, ensureDir, ensureFile, writeFile, readdir, copy, } = require("fs-extra"); const Repository = require("./repository"); const chance = require("chance").Chance(); describe("Testing object creation functionality", () => { const sourceData = "./test-data/simple-ocfl-object"; let source; const base = path.join(__dirname, "..", "filesystem-repo-ocfl1"); const ocflRoot = path.join(base, chance.word()); const ocflScratch = path.join(base, chance.word()); let repository, object; beforeAll(async () => { await ensureDir(ocflRoot); await ensureDir(ocflScratch); repository = new Repository({ ocflRoot, ocflScratch }); await repository.create(); }); beforeEach(async () => { source = path.join(ocflScratch, chance.word()); await copy(sourceData, source); }); afterEach(async () => { await remove(source); }); afterAll(async () => { await remove(base); }); test("should be able to create an object with one version", async () => { object = repository.object({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); let inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v1"); expect(Object.keys(inventory.versions).length).toEqual(1); expect(inventory.manifest).toEqual({ "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); }); test(`should fail to create an object because there's already one in deposit`, async () => { let id = chance.hash(); await ensureDir(path.join(ocflScratch, "deposit", id)); try { object = repository.object({ id: chance.hash() }); await object.update({ source }); } catch (error) { expect(error.message).toEqual( "An object with that ID is already in the deposit path." ); } }); test.skip(`should be able to create an object given a path`, async () => { const objectPath = "/xx/yy/zz"; object.init({ objectPath }); await object.update({ source }); await object.load(); const inventory = await object.getLatestInventory(); expect(inventory.id).toEqual(objectPath); }); test(`should fail to create an object as a child of another`, async () => { let id = "/xx/yy"; object = repository.object({ id }); await object.update({ source }); id2 = `${id}/zz`; object = repository.object({ id: id2 }); try { await object.update({ source }); } catch (error) { expect(error.message).toEqual( `This object is a child of an existing object and that's not allowed.` ); } }); test.skip(`should be able to load an object from a path`, async () => { const id = chance.hash(); object.init({ id }); await object.update({ source }); await object.load(); const inventory = await object.getLatestInventory(); expect(inventory.manifest).toEqual({ "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); }); test("should be able to create an object with two versions by adding a file", async () => { object = repository.object({ id: chance.hash() }); // v1 await object.update({ source }); // v2 add a file let file = path.join(source, "file1.txt"); await writeFile(file, "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); let inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v2"); expect(Object.keys(inventory.versions).length).toEqual(2); expect(inventory.manifest).toEqual({ de01d675497715d6e139c1182eeb4e9c73cfe25df4f1006a8e75679910c7b897707591bd574f27f21c50a6f624e737284c5271431302afa0bac8b66a342e3617: [ "v2/content/file1.txt", ], "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); }); test("should be able to create an object with three versions by adding another file", async () => { object = repository.object({ id: chance.hash() }); // v1 await object.update({ source }); // v2 add a file let file1 = path.join(source, "file1.txt"); await writeFile(file1, "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); let inventory = await object.getLatestInventory(); // v3 add another file let file2 = path.join(source, "file2.txt"); await writeFile(file2, "fsf v$T)(*SKGJKVJS DFKJs"); await object.update({ source }); inventory = await object.getLatestInventory(); expect(inventory.head).toEqual("v3"); expect(Object.keys(inventory.versions).length).toEqual(3); }); test("should be able to create an object with four versions by changing an existing file", async () => { object = repository.object({ id: chance.hash() }); await object.update({ source }); // v2 add a file await writeFile(path.join(source, "file1.txt"), "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // v3 add another file await writeFile(path.join(source, "file2.txt"), "fsf v$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // v4 change the content of a file await writeFile(path.join(source, "file1.txt"), "fsf v$T)(*SKGJKVJS DFKJs"); await object.update({ source }); let inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v4"); expect(Object.keys(inventory.versions).length).toEqual(4); expect(inventory.manifest).toEqual({ ea832e64995b2a7d64358abe682e9c0abfb015d22278a78ac17cb2d0a0ac2f9dfc02f5fb2e6baf37a77480a5be0b32957d0e9f979a77403ffb70a8dae1e319d8: [ "v4/content/file1.txt", "v3/content/file2.txt", ], "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); }); test("should be able to create an object with five versions by removing an existing file", async () => { object.init({ id: chance.hash() }); // v1 await object.update({ source }); // v2 add a file await writeFile(path.join(source, "file1.txt"), "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // v3 add another file await writeFile(path.join(source, "file2.txt"), "fsf v$T)(*SKGJKVJS DFKJs"); await object.update({ source }); await // v4 change the content of a file await writeFile(path.join(source, "file1.txt"), "fsf v$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // v5 remove a file await remove(path.join(source, "file2.txt")); await object.update({ source }); let inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v5"); expect(Object.keys(inventory.versions).length).toEqual(5); expect(inventory.manifest).toEqual({ ea832e64995b2a7d64358abe682e9c0abfb015d22278a78ac17cb2d0a0ac2f9dfc02f5fb2e6baf37a77480a5be0b32957d0e9f979a77403ffb70a8dae1e319d8: [ "v4/content/file1.txt", ], "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); }); test("should remain at one version when there is no change to the source", async () => { object = repository.object({ id: chance.hash() }); await object.update({ source }); await object.update({ source }); await object.update({ source }); let inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v1"); expect(Object.keys(inventory.versions).length).toEqual(1); expect(inventory.manifest).toEqual({ "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); }); test("should be able to create an object with a callback to write the content", async () => { object = repository.object({ id: chance.hash() }); await object.update({ writer: writeContent }); let inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v1"); expect(Object.keys(inventory.versions).length).toEqual(1); expect(inventory.manifest).toEqual({ "3abea15c00b706b25bd01ef29bfbe4fc117bb3464d0ba178bb2d924d71b758dd660dbef464e896ad4ec86e4bb498a7b58abb11b958875c468193cc42995b249e": [ "v1/content/dir/fileX.txt", ], "9fb7cdf68ceaa2425e9e8f761e6b89c95250f63ae014c757436760caf9c28b2b368a173b340616ccbba0377c0c724a2d67a6c968a3b91968f72a8ed0da95e6cf": [ "v1/content/fileY.txt", ], }); }); test("should handle an object being written with a source folder and a callback", async () => { object = repository.object({ id: chance.hash() }); // v1 - load a source folder await object.update({ source }); // v2 - write some content via a callback await object.update({ writer: writeContent }); inventory = await object.getLatestInventory(); // console.log(inventory); expect(inventory.head).toEqual("v2"); expect(Object.keys(inventory.versions).length).toEqual(2); expect(inventory.manifest).toEqual({ "3abea15c00b706b25bd01ef29bfbe4fc117bb3464d0ba178bb2d924d71b758dd660dbef464e896ad4ec86e4bb498a7b58abb11b958875c468193cc42995b249e": [ "v2/content/dir/fileX.txt", ], "9fb7cdf68ceaa2425e9e8f761e6b89c95250f63ae014c757436760caf9c28b2b368a173b340616ccbba0377c0c724a2d67a6c968a3b91968f72a8ed0da95e6cf": [ "v2/content/fileY.txt", ], }); }); test("should object to both source and writer being defined", async () => { object = repository.object({ id: chance.hash() }); try { await object.update({ source, writer: () => {} }); } catch (error) { expect(error.message).toEqual( "Specify only one of 'source', 'writer' or 'removeFiles'." ); } }); test("should object because neither source nor writer is defined", async () => { object = repository.object({ id: chance.hash() }); try { await object.update({}); } catch (error) { expect(error.message).toEqual( "Specify at least one of 'source', 'writer' or 'removeFiles'." ); } }); test(`should not be able to export - target folder doesn't exist`, async () => { object = repository.object({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); try { await object.export({ target: "./notfolder" }); } catch (error) { expect(error.message).toEqual(`Export target folder doesn't exist.`); } }); test(`should not be able to export - target folder not empty`, async () => { object = repository.object({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); try { await object.export({ target: "./test-data" }); } catch (error) { expect(error.message).toEqual(`Export target folder isn't empty.`); } }); test("should be able to export an object with one version - automatically select head", async () => { object = repository.object({ id: chance.hash() }); const exportFolder = path.join(ocflScratch, chance.hash()); await object.update({ source }); await ensureDir(exportFolder); await object.export({ target: exportFolder }); let content = await readdir(exportFolder); expect(content).toEqual(["sample"]); content = await readdir(path.join(exportFolder, "sample")); expect(content).toEqual(["file_0.txt"]); await remove(exportFolder); }); test("should be able to export an object with one version - select v1", async () => { object = repository.object({ id: chance.hash() }); const exportFolder = path.join(ocflScratch, chance.hash()); await object.update({ source }); await ensureDir(exportFolder); await object.export({ target: exportFolder, version: "v1" }); let content = await readdir(exportFolder); expect(content).toEqual(["sample"]); content = await readdir(path.join(exportFolder, "sample")); expect(content).toEqual(["file_0.txt"]); await remove(exportFolder); }); test("should be able to export a version from an object with two versions - select v1", async () => { object = repository.object({ id: chance.hash() }); const exportFolder = path.join(ocflScratch, chance.hash()); // v1 await object.update({ source }); // v2 add a file await writeFile(path.join(source, "file1.txt"), "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // export await ensureDir(exportFolder); await object.export({ target: exportFolder, version: "v1" }); let content = await readdir(exportFolder); expect(content).toEqual(["sample"]); content = await readdir(path.join(exportFolder, "sample")); expect(content).toEqual(["file_0.txt"]); await remove(exportFolder); }); test("should be able to export a version from an object with two versions - select v2", async () => { object = repository.object({ id: chance.hash() }); const exportFolder = path.join(ocflScratch, chance.hash()); // v1 await object.update({ source }); // v2 add a file await writeFile(path.join(source, "file1.txt"), "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // export await ensureDir(exportFolder); await object.export({ target: exportFolder, version: "v2" }); let content = await readdir(exportFolder); expect(content).toEqual(["file1.txt", "sample"]); content = await readdir(path.join(exportFolder, "sample")); expect(content).toEqual(["file_0.txt"]); await remove(exportFolder); }); async function writeContent({ target }) { const CONTENT = { "dir/fileX.txt": "Contents of fileX.txt", "fileY.txt": "Contents of fileY.txt", }; const files = Object.keys(CONTENT); for (const f of files) { const d = path.join(target, path.dirname(f)); await ensureDir(d); await writeFile(path.join(target, f), CONTENT[f]); } } }); describe("Testing object manipulation functionality - object with one version", () => { const sourceData = "./test-data/simple-ocfl-object"; let source; const base = path.join(__dirname, "..", "filesystem-repo-ocfl2"); const ocflRoot = path.join(base, chance.word()); const ocflScratch = path.join(base, chance.word()); let repository, object; beforeAll(async () => { await ensureDir(ocflRoot); await ensureDir(ocflScratch); repository = new Repository({ ocflRoot, ocflScratch }); await repository.create(); }); beforeEach(async () => { source = path.join(ocflScratch, chance.word()); await copy(sourceData, source); }); afterEach(async () => { await remove(source); }); afterAll(async () => { await remove(base); }); test("should be able to get the latest inventory", async () => { object = repository.object({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); const inventory = await object.getLatestInventory(); expect(inventory.head).toEqual("v1"); }); test("should be able to get the v1 inventory", async () => { object = repository.object({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); const inventory = await object.getInventory({ version: "v1" }); expect(inventory.head).toEqual("v1"); }); test("should be able to get the versions from an object with one version", async () => { object = repository.object({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); let versions = await object.getVersions(); expect(versions.length).toEqual(1); expect(versions[0].version).toEqual("v1"); }); test(`should be able to see if it's an OCFL object`, async () => { object = repository.object({ id: chance.hash() }); let isObject = await object.isObject(); expect(isObject).toBe(false); // v1 create object and import folder await object.update({ source }); isObject = await object.isObject(); expect(isObject).toBe(true); }); test(`should be able to see if it's available`, async () => { object = repository.object({ id: chance.hash() }); let isAvailable = await object.isAvailable(); expect(isAvailable).toBe(true); // v1 create object and import folder await object.update({ source }); isAvailable = await object.isAvailable(); expect(isAvailable).toBe(false); }); test("should be able to get the state from an object with one version", async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); let versions = await object.getVersions(); expect(versions.length).toEqual(1); expect(versions[0].version).toEqual("v1"); let content = await object.getVersion({ version: "v1" }); expect(content.version).toEqual("v1"); expect(content.state).toEqual({ "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); }); test("should be able to get the latest state from an object with one version", async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); let versions = await object.getVersions(); expect(versions.length).toEqual(1); expect(versions[0].version).toEqual("v1"); let content = await object.getLatestVersion(); expect(content.version).toEqual("v1"); expect(content.state).toEqual({ "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); }); test("should be able to get all states from an object with one version", async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); let versions = await object.getVersions(); expect(versions.length).toEqual(1); expect(versions[0].version).toEqual("v1"); let content = await object.getAllVersions(); expect(content.length).toEqual(1); content = content.pop(); expect(content.version).toEqual("v1"); expect(content.state).toEqual({ "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); }); test("should be able to remove an object from the repository", async () => { object.init({ id: chance.hash() }); await object.update({ source }); let isObject = await object.isObject(); expect(isObject).toBe(true); const result = await object.remove(); expect(result).toBe(null); isObject = await object.isObject(); expect(isObject).toBe(false); isAvailable = await object.isAvailable(); expect(isAvailable).toBe(true); }); test(`should be able to resolve a file path relative to the object`, async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.load(); let version = await object.getLatestVersion(); let file = version.state["file_0.txt"].pop(); file = object.resolveFilePath({ filePath: file.path }); expect(file).toMatch(/v1\/content\/sample\/file_0.txt/); }); }); describe("Testing object manipulation functionality - merging and deleting content", () => { const sourceData = "./test-data/simple-ocfl-object"; const base = path.join(__dirname, "..", "filesystem-repo-ocfl3"); const ocflRoot = path.join(base, chance.word()); const ocflScratch = path.join(base, chance.word()); let repository, source, file1, file2; beforeAll(async () => { await remove(base); await ensureDir(ocflRoot); await ensureDir(ocflScratch); repository = new Repository({ ocflRoot, ocflScratch }); await repository.create(); }); beforeEach(async () => { source = path.join(ocflScratch, chance.word()); await copy(sourceData, source); file1 = path.join(source, "file1.txt"); file2 = path.join(source, "file2.txt"); }); afterEach(async () => { await remove(source); }); afterAll(async () => { await remove(base); }); test("it should be able to merge in a file without having the whole current object", async () => { let object = repository.object({ id: chance.hash() }); // console.log(object); await object.update({ source }); let version = await object.getLatestVersion(); version = await object.getLatestVersion(); expect(version.version).toEqual("v1"); const newstuff = path.join(ocflScratch, "new-stuff"); await ensureDir(newstuff); await writeFile(path.join(newstuff, "new-file.txt"), "some content"); await object.update({ source: newstuff, updateMode: "merge" }); let data = await object.getLatestVersion(); expect(data.version).toEqual("v2"); expect(Object.keys(data.state).sort()).toEqual([ "file_0.txt", "new-file.txt", ]); expect(data.state["new-file.txt"][0].path).toEqual( "v2/content/new-file.txt" ); expect(data.state["new-file.txt"][0].version).toEqual(2); expect(data.state["file_0.txt"][0].path).toEqual( "v1/content/sample/file_0.txt" ); expect(data.state["file_0.txt"][0].version).toEqual(1); let inventory = await object.getLatestInventory(); expect(inventory.head).toEqual("v2"); expect(inventory.manifest).toEqual({ "65c256c639bd6dd483be341831c19a3996954901bb2a07f79593f3e3af5692559bdb124d099c2b92ced7e59b7ed02d3b7f42d50740d999bebd91983db2842762": [ "v2/content/new-file.txt", ], "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99": [ "v1/content/sample/file_0.txt", ], }); await remove(newstuff); }); test("it should be able to delete a file without having the whole current object", async () => { let object = repository.object({ id: chance.hash() }); await writeFile(file1, "some content"); await writeFile(file2, "some content"); // console.log(object); await object.update({ source }); await object.update({ removeFiles: ["file1.txt"] }); let data = await object.getLatestVersion(); expect(data.version).toEqual("v2"); expect(Object.keys(data.state).sort()).toEqual(["file2.txt", "file_0.txt"]); expect(data.state["file2.txt"][0].path).toEqual("v1/content/file2.txt"); expect(data.state["file2.txt"][0].version).toEqual(1); expect(data.state["file_0.txt"][0].path).toEqual( "v1/content/sample/file_0.txt" ); expect(data.state["file_0.txt"][0].version).toEqual(1); }); test("it should be able to delete multiple files without having the whole current object", async () => { let object = repository.object({ id: chance.hash() }); await writeFile(file1, "some content"); await writeFile(file2, "some content"); // console.log(object); await object.update({ source }); await object.update({ removeFiles: ["file1.txt", "sample/file_0.txt"] }); let data = await object.getLatestVersion(); expect(data.version).toEqual("v2"); expect(Object.keys(data.state).sort()).toEqual(["file2.txt"]); expect(data.state["file2.txt"][0].path).toEqual("v1/content/file2.txt"); expect(data.state["file2.txt"][0].version).toEqual(1); }); }); describe("Testing object manipulation functionality - object with three versions", () => { const sourceData = "./test-data/simple-ocfl-object"; const base = path.join(__dirname, "..", "filesystem-repo-ocfl4"); const ocflRoot = path.join(base, chance.word()); const ocflScratch = path.join(base, chance.word()); let repository, source, object; beforeAll(async () => { await ensureDir(ocflRoot); await ensureDir(ocflScratch); repository = new Repository({ ocflRoot, ocflScratch }); await repository.create(); object = repository.object({ id: chance.hash() }); source = path.join(ocflScratch, chance.word()); await copy(sourceData, source); // v1 await object.update({ source }); // v2 add a file await writeFile(path.join(source, "file1.txt"), "$T)(*SKGJKVJS DFKJs"); await object.update({ source }); // v3 add another file await writeFile(path.join(source, "file2.txt"), "fsf v$T)(*SKGJKVJS DFKJs"); await object.update({ source }); await object.load(); }); afterAll(async () => { await remove(base); }); test("should be able to get the latest inventory", async () => { // v1 create object and import folder await object.update({ source }); await object.load(); const inventory = await object.getLatestInventory(); expect(inventory.head).toEqual("v3"); }); test("should be able to get the v1 inventory", async () => { // v1 create object and import folder await object.update({ source }); await object.load(); const inventory = await object.getInventory({ version: "v1" }); expect(inventory.head).toEqual("v1"); }); test("should be able to get the versions from an object with three versions", async () => { let versions = await object.getVersions(); expect(versions.length).toEqual(3); expect(versions.pop().version).toEqual("v3"); }); test("should be able to get the v1 state from an object with three versions", async () => { let content = await object.getVersion({ version: "v1" }); expect(content.version).toEqual("v1"); expect(content.state).toEqual({ "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); }); test("should be able to get the latest state from an object with one version", async () => { let content = await object.getLatestVersion(); expect(content.version).toEqual("v3"); expect(content.state).toEqual({ "file1.txt": [ { name: "file1.txt", path: "v2/content/file1.txt", hash: "de01d675497715d6e139c1182eeb4e9c73cfe25df4f1006a8e75679910c7b897707591bd574f27f21c50a6f624e737284c5271431302afa0bac8b66a342e3617", version: 2, }, ], "file2.txt": [ { name: "file2.txt", path: "v3/content/file2.txt", hash: "ea832e64995b2a7d64358abe682e9c0abfb015d22278a78ac17cb2d0a0ac2f9dfc02f5fb2e6baf37a77480a5be0b32957d0e9f979a77403ffb70a8dae1e319d8", version: 3, }, ], "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); }); test("should be able to get all states from an object with one version", async () => { let versions = await object.getVersions(); let content = await object.getAllVersions(); expect(content.length).toEqual(3); const state1 = content.shift(); expect(state1.version).toEqual("v1"); expect(state1.state).toEqual({ "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); const state2 = content.shift(); expect(state2.version).toEqual("v2"); expect(state2.state).toEqual({ "file1.txt": [ { name: "file1.txt", path: "v2/content/file1.txt", hash: "de01d675497715d6e139c1182eeb4e9c73cfe25df4f1006a8e75679910c7b897707591bd574f27f21c50a6f624e737284c5271431302afa0bac8b66a342e3617", version: 2, }, ], "file_0.txt": [ { name: "file_0.txt", path: "v1/content/sample/file_0.txt", hash: "31bca02094eb78126a517b206a88c73cfa9ec6f704c7030d18212cace820f025f00bf0ea68dbf3f3a5436ca63b53bf7bf80ad8d5de7d8359d0b7fed9dbc3ab99", version: 1, }, ], }); }); }); describe(`Test two stage update / commit and diff'ing objects`, () => { const sourceData = "./test-data/simple-ocfl-object"; const base = path.join(__dirname, "..", "filesystem-repo-ocfl5"); const ocflRoot = path.join(base, chance.word()); const ocflScratch = path.join(base, chance.word()); let repository, object; beforeAll(async () => { await ensureDir(ocflRoot); await ensureDir(ocflScratch); repository = new Repository({ ocflRoot, ocflScratch }); await repository.create(); object = repository.object({ id: chance.hash() }); }); beforeEach(async () => { source = path.join(ocflScratch, chance.word()); await copy(sourceData, source); }); afterEach(async () => { await remove(source); }); afterAll(async () => { await remove(base); }); test("it should be able to break out of a v1 update and then commit later", async () => { const id = chance.hash(); object = repository.object({ id }); // v1 let { inventory } = await object.update({ source, commit: false }); await object.commit({ inventory }); }); test("it should fail gracefully trying to update twice in a multi stage commit", async () => { const id = chance.hash(); object = repository.object({ id }); // v1 let { inventory } = await object.update({ source, commit: false }); // try second update try { ({ inventory } = await object.update({ source, commit: false })); } catch (error) { expect(error.message).toBe( `This object is already in deposit. If this is a two stage update commit the last changes before updating again.` ); } await object.commit({ inventory }); // try again after commit await writeFile(path.join(source, "fileX.txt"), "sfdkghsfgsdfg"); ({ inventory } = await object.update({ source, commit: false })); await object.commit({ inventory }); }); test("it should fail trying to diff an object with one version", async () => { const id = chance.hash(); object = repository.object({ id }); // v1 let { inventory } = await object.update({ source, commit: false }); let versions = await object.getVersions(); versions = { next: versions.pop().version, previous: versions.pop()?.version, }; try { diff = await object.diffVersions(versions); } catch (error) { expect(error.message).toEqual( `previous can only be a version number like 'v1'` ); } await object.commit({ inventory }); }); test("should be able to return a diff of two versions", async () => { object.init({ id: chance.hash() }); // v1 await object.update({ source }); // v2 await writeFile(path.join(source, "file2.txt"), "stuff"); await object.update({ source }); let diff = await object.diffVersions({ previous: "v1", next: "v2" }); expect(diff).toEqual({ same: ["v1/content/sample/file_0.txt"], previous: [], next: ["v2/content/file2.txt"], }); // v3 await object.update({ removeFiles: ["sample/file_0.txt"] }); diff = await object.diffVersions({ previous: "v2", next: "v3" }); expect(diff).toEqual({ same: ["v2/content/file2.txt"], previous: ["v1/content/sample/file_0.txt"], next: [], }); }); test("it should be able to break out of an update and diff two versions", async () => { const id = chance.hash(); object = repository.object({ id }); // v1 await object.update({ source }); // v2 - two stage commit await writeFile(path.join(source, "file2.txt"), "stuff"); let { inventory } = await object.update({ source, commit: false, }); let versions = await object.getVersions(); versions = { next: versions.pop().version, previous: versions.pop().version, }; let diff = await object.diffVersions(versions); let decide = diff.next.filter( (filename) => !filename.match(/repo-metadata/) ); expect(decide.length).toBe(1); await object.commit({ inventory }); // v3 - two stage commit await writeFile(path.join(source, "repo-metadata.json"), "other stuff"); ({ inventory } = await object.update({ source, commit: false, })); versions = await object.getVersions(); versions = { next: versions.pop().version, previous: versions.pop().version, }; diff = await object.diffVersions(versions); decide = diff.next.filter((filename) => !filename.match(/repo-metadata/)); expect(decide.length).toBe(0); await object.commit({ inventory }); }); test("it should be able to break out of an update and discover files in deposit that are not inventoried", async () => { const id = chance.hash(); object = repository.object({ id }); // v1 await object.update({ source }); // v2 - two stage commit await writeFile(path.join(source, "file2.txt"), "stuff"); let { inventory } = await object.update({ source, commit: false, }); await writeFile(path.join(object.depositPath, "file3.txt"), "stuff"); try { await object.commit({ inventory }); } catch (error) { expect(error.message).toEqual( "The object does not verify. Aborting this commit." ); } await object.remove(); }); }); describe(`Test verifying objects`, () => { const sourceData = "./test-data/simple-ocfl-object"; const base = path.join(__dirname, "..", "filesystem-repo-ocfl6"); const ocflRoot = path.join(base, chance.word()); const ocflScratch = path.join(base, chance.word()); let repository, object; beforeAll(async () => { await ensureDir(ocflRoot); await ensureDir(ocflScratch); repository = new Repository({ ocflRoot, ocflScratch }); await repository.create(); object = repository.object({ id: chance.hash() }); source = path.join(ocflScratch, chance.word()); await copy(sourceData, source); }); afterAll(async () => { await remove(base); }); test(`a known good object should verify`, async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.update({ writer: writeContent }); let { isValid, errors } = await object.verify(); expect(isValid).toBe(true); expect(errors.length).toBe(0); }); test(`an object with an inventoried file missing should not verify`, async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await object.update({ writer: writeContent }); let { isValid, errors } = await object.verify(); expect(isValid).toBe(true); expect(errors.length).toBe(0); const version = await object.getLatestVersion(); await remove( path.join(object.repositoryPath, version.state["fileX.txt"][0].path) ); ({ isValid, errors } = await object.verify()); expect(isValid).toBe(false); expect(errors).toEqual([ "'v2/content/dir/fileX.txt' is inventoried but does not exist within the object", ]); }); test(`an object with an extra file that is not inventoried should not verify`, async () => { object.init({ id: chance.hash() }); // v1 create object and import folder await object.update({ source }); await writeFile( path.join(object.repositoryPath, "v1", "content", "extra-file.txt"), "content" ); ({ isValid, errors } = await object.verify()); console.log(isValid, errors); expect(isValid).toBe(false); expect(errors).toEqual([ "The object has a file 'v1/content/extra-file.txt' that is not in the inventory", ]); }); async function writeContent({ target }) { CONTENT = { "dir/fileX.txt": "Contents of fileX.txt", "fileY.txt": "Contents of fileY.txt", "repo-metadata/metadata.json": "{}", }; const files = Object.keys(CONTENT); for (const f of files) { const d = path.join(target, path.dirname(f)); await ensureDir(d); await writeFile(path.join(target, f), CONTENT[f]); } } });