Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/sync/editor/db/test/doc.test.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { DBDocument, from_str } from "../doc";6import { fromJS } from "immutable";78describe("create a DBDocument with one primary key, and call methods on it", () => {9let doc: DBDocument;10const records = fromJS([11{ key: "cocalc", value: "sagemath" },12{ key: "cloud", value: "collaboration" },13]);1415it("creates the db-doc", () => {16doc = new DBDocument(new Set(["key"]), new Set([]), records);17expect(`${doc}`).toBe("[object Object]");18});1920it("convert to string", () => {21expect(doc.to_str()).toBe(22'{"key":"cloud","value":"collaboration"}\n{"key":"cocalc","value":"sagemath"}'23);24});2526it("count gives number of records", () => {27expect(doc.count()).toBe(2);28});2930it("checks equality", () => {31expect(doc.is_equal()).toBe(false);32expect(doc.is_equal(undefined)).toBe(false);33expect(doc.is_equal(doc)).toBe(true);34expect(35doc.is_equal(new DBDocument(new Set(["key"]), new Set([]), records))36).toBe(true);37expect(doc.is_equal(new DBDocument(new Set(["key"]), new Set([])))).toBe(38false39);40});4142it("make and apply a patch", () => {43const doc2 = doc44.set([45{ key: "new", value: "value" },46{ key: "cloud", value: "computing" },47])48.delete({ key: "cocalc" });49const patch = doc.make_patch(doc2);50expect(patch).toEqual([51-1,52[{ key: "cocalc" }],531,54[55{ key: "new", value: "value" },56{ key: "cloud", value: "computing" },57],58]);59expect(doc.apply_patch(patch).is_equal(doc2)).toBe(true);6061// Apply to doc2 instead -- obviously shouldn't work.62expect(doc2.apply_patch(patch).is_equal(doc)).toBe(false);6364// Let's also make a patch in the other direction from doc2 to doc:65const patch2 = doc2.make_patch(doc);66expect(patch2).toEqual([67-1,68[{ key: "new" }],691,70[71{ key: "cocalc", value: "sagemath" },72{ key: "cloud", value: "collaboration" },73],74]);75expect(doc2.apply_patch(patch2).is_equal(doc)).toBe(true);76});7778it("tests get_one", () => {79let x = doc.get_one({ key: "cloud" });80if (x == null) throw Error("bug");81expect(x.toJS()).toEqual({ key: "cloud", value: "collaboration" });8283// can only search on primary keys84expect(() => doc.get_one({ value: "collaboration" })).toThrow(85"must be a primary key"86);8788x = doc.get_one({});89if (x == null) throw Error("bug");90expect(x.toJS()).toEqual({ key: "cocalc", value: "sagemath" });91});9293it("tests get", () => {94expect(doc.get({}).toJS()).toEqual([95{ key: "cocalc", value: "sagemath" },96{ key: "cloud", value: "collaboration" },97]);98expect(doc.get({ key: "cloud" }).toJS()).toEqual([99{ key: "cloud", value: "collaboration" },100]);101102// can only search on primary keys103expect(() => doc.get({ value: "collaboration" })).toThrow(104"must be a primary key"105);106});107108it("tests delete", () => {109const doc2 = doc.delete({ key: "cloud" });110expect(doc2.to_str()).toBe('{"key":"cocalc","value":"sagemath"}');111});112113it("tests set changing a field", () => {114const doc2 = doc.set({ key: "cloud", value: "computing" });115expect(doc2.get({ key: "cloud" }).toJS()).toEqual([116{ key: "cloud", value: "computing" },117]);118});119120it("tests set adding a new field", () => {121const doc2 = doc.set({ key: "cloud", other: [1, 2, 3] });122expect(doc2.get({ key: "cloud" }).toJS()).toEqual([123{ key: "cloud", other: [1, 2, 3], value: "collaboration" },124]);125});126});127128describe("test various types of patches", () => {129it("string patch -- atomic", () => {130const doc = new DBDocument(131new Set(["key"]),132new Set([]),133fromJS([{ key: "cocalc", value: "a string" }])134);135const patch = doc.make_patch(136doc.set({ key: "cocalc", value: "a different string" })137);138expect(patch).toEqual([1391,140[{ key: "cocalc", value: "a different string" }],141]);142143// And deleting that field:144const patch2 = doc.make_patch(doc.set({ key: "cocalc", value: null }));145expect(patch2).toEqual([1, [{ key: "cocalc", value: null }]]);146// And yes, it's really deleted:147expect(doc.apply_patch(patch2).to_str()).toBe('{"key":"cocalc"}');148});149150it("string patch -- diff", () => {151const doc = new DBDocument(152new Set(["key"]),153new Set(["value"]) /* so uses diffs */,154fromJS([{ key: "cocalc", value: "a string" }])155);156const patch = doc.make_patch(157doc.set({ key: "cocalc", value: "a different string" })158);159expect(patch).toEqual([1601,161[162{163key: "cocalc",164value: [165[166[167[0, "a "],168[1, "different "],169[0, "string"],170],1710,1720,1738,17418,175],176],177},178],179]);180181// And deleting that field, is the same still:182const patch2 = doc.make_patch(doc.set({ key: "cocalc", value: null }));183expect(patch2).toEqual([1, [{ key: "cocalc", value: null }]]);184});185186it("map patch -- our efficient about diffs (NOT atomic)", () => {187const doc = new DBDocument(188new Set(["key"]),189new Set([]),190fromJS([{ key: "cocalc", value: { one: 1, two: 2, five: 5 } }])191);192193// This really sets just the two part:194const doc2 = doc.set({ key: "cocalc", value: { two: "two" } });195expect(doc2.to_str()).toBe(196'{"key":"cocalc","value":{"five":5,"one":1,"two":"two"}}'197);198199// That's why this patch can be compact:200const patch2 = doc.make_patch(doc2);201expect(patch2).toEqual([1, [{ key: "cocalc", value: { two: "two" } }]]);202203// To completely change the value atomically of a map,204// you have to set to null (deleting that field),205// then set the new value:206let doc3 = doc.set({ key: "cocalc", value: null });207doc3 = doc3.set({ key: "cocalc", value: { two: "two" } });208expect(doc3.to_str()).toBe('{"key":"cocalc","value":{"two":"two"}}');209const patch3 = doc.make_patch(doc3);210// It's smart and just puts the deletes in as null setting, rather than211// using the two steps we did above:212expect(patch3).toEqual([2131,214[{ key: "cocalc", value: { five: null, one: null, two: "two" } }],215]);216// And confirm this patch "works":217expect(doc.apply_patch(patch3).is_equal(doc3)).toBe(true);218});219220it("array patches are just stupidly atomic", () => {221const doc = new DBDocument(222new Set(["key"]),223new Set([]),224fromJS([{ key: "cocalc", value: [1, 2, 5] }])225);226227// This really sets just the two part:228const doc2 = doc.set({ key: "cocalc", value: [1, "2", 5] });229expect(doc2.to_str()).toBe('{"key":"cocalc","value":[1,"2",5]}');230231// The patch is NOT in any way **compact** -- it just232// sets the whole array.233const patch2 = doc.make_patch(doc2);234expect(patch2).toEqual([1, [{ key: "cocalc", value: [1, "2", 5] }]]);235});236237it("tests that string column runtime type checking works", () => {238// This checks that https://github.com/sagemathinc/cocalc/issues/3625239// is fixed.240const doc = new DBDocument(241new Set(["key"]),242new Set(["value"]) /* so must always be a string */,243fromJS([{ key: "cocalc", value: "a string" }])244);245246const doc2 = doc.set({ value: "foo" });247const x = doc2.get_one({})!;248expect(x.get("value")).toBe("foo");249250expect(() => doc2.set({ value: 0 })).toThrow("must be a string");251});252});253254describe("test conversion to and *from* strings", () => {255it("makes a doc with various data types", () => {256const doc = new DBDocument(257new Set(["key"]),258new Set([]),259fromJS([260{261key: "cocalc",262string: "cocalc",263number: 389,264map: { lat: 5, long: 7 },265list: ["milk", "cookies", { a: true }],266boolean: true,267},268{ key: [1, { a: 5 }], number: 37 },269])270);271272const s = doc.to_str();273expect(s).toBe(274'{"boolean":true,"key":"cocalc","list":["milk","cookies",{"a":true}],"map":{"lat":5,"long":7},"number":389,"string":"cocalc"}\n{"key":[1,{"a":5}],"number":37}'275);276277const doc2 = from_str(s, ["key"], []);278expect(doc2.is_equal(doc)).toBe(true);279280// Equality testing ignores primary281// keys and string cols, since it's assumed282// that Documents are only compared when283// these are the same, since the application284// is to SyncDoc.285const doc3 = from_str(s, ["key"], ["string"]);286expect(doc3.is_equal(doc)).toBe(true);287const doc4 = from_str(s, ["key", "number"], []);288expect(doc4.is_equal(doc)).toBe(true);289});290});291292describe("test using a compound primary key", () => {293// Using a compound primary key lets you have several294// separate database tables in the same db-doc.295296const doc = new DBDocument(297new Set(["table", "id"]),298new Set([]),299fromJS([300{301table: "accounts",302id: 123,303name: "CoCalc User",304},305{ table: "projects", id: 123, title: "Test project" },306])307);308309it("tests searches", () => {310expect(doc.get({ table: "accounts", id: 123 }).toJS()).toEqual([311{ id: 123, name: "CoCalc User", table: "accounts" },312]);313expect(doc.get({ table: "projects", id: 123 }).toJS()).toEqual([314{ id: 123, table: "projects", title: "Test project" },315]);316317// type does matter318expect(doc.get({ table: "accounts", id: "123" }).toJS()).toEqual([]);319});320321it("tests doing a set of two records (one change and one create)", () => {322const doc2 = doc.set([323{ table: "accounts", id: 123, name: "CoCalc Sage" },324{ table: "accounts", id: 124, name: "Sage Math" },325]);326expect(doc2.get({ table: "accounts" }).toJS()).toEqual([327{ id: 123, name: "CoCalc Sage", table: "accounts" },328{ id: 124, name: "Sage Math", table: "accounts" },329]);330});331});332333334