test: improve overall test coverage

This commit is contained in:
thecodrr
2022-08-15 10:57:25 +05:00
parent 6d45c23e79
commit 87b778db94
25 changed files with 908 additions and 714 deletions

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`get offer code: offer-code 1`] = `"123"`;

View File

@@ -0,0 +1,77 @@
import { databaseTest, noteTest, StorageInterface } from "../__tests__/utils";
const user = {
email: process.env.EMAIL,
password: process.env.PASSWORD,
hashedPassword: process.env.HASHED_PASSWORD,
};
jest.setTimeout(15 * 1000);
beforeEach(() => {
StorageInterface.clear();
});
afterAll(async () => {
const db = await databaseTest();
await db.monographs.init();
for (const id of db.monographs.monographs) {
await db.monographs.unpublish(id);
}
StorageInterface.clear();
});
// test("get monographs", () =>
// databaseTest().then(async (db) => {
// await db.user.login(user.email, user.password, user.hashedPassword);
// await db.monographs.init();
// expect(db.monographs.all).toBeGreaterThanOrEqual(0);
// }));
test("publish a monograph", () =>
noteTest().then(async ({ db, id }) => {
await db.user.login(user.email, user.password, user.hashedPassword);
await db.monographs.init();
const monographId = await db.monographs.publish(id);
expect(db.monographs.all.find((m) => m.id === id)).toBeDefined();
const monograph = await db.monographs.get(monographId);
const note = db.notes.note(id);
expect(monograph.id).toBe(monographId);
expect(monograph.title).toBe(note.title);
}));
test("update a published monograph", () =>
noteTest().then(async ({ db, id }) => {
await db.user.login(user.email, user.password, user.hashedPassword);
await db.monographs.init();
const monographId = await db.monographs.publish(id);
let monograph = await db.monographs.get(monographId);
const note = db.notes.note(id);
expect(monograph.title).toBe(note.title);
const editedTitle = "EDITED TITLE OF MY NOTE!";
await db.notes.add({ id, title: editedTitle });
await db.monographs.publish(id);
monograph = await db.monographs.get(monographId);
expect(monograph.title).toBe(editedTitle);
}));
test("unpublish a monograph", () =>
noteTest().then(async ({ db, id }) => {
await db.user.login(user.email, user.password, user.hashedPassword);
await db.monographs.init();
await db.monographs.publish(id);
expect(db.monographs.all.find((m) => m.id === id)).toBeDefined();
await db.monographs.unpublish(id);
expect(db.monographs.all.find((m) => m.id === id)).toBeUndefined();
}));

View File

@@ -0,0 +1,18 @@
import hosts from "../utils/constants";
import Offers from "../api/offers";
test("get offer code", async () => {
const offers = new Offers();
hosts.SUBSCRIPTIONS_HOST = "https://subscriptions.streetwriters.co";
expect(await offers.getCode("TESTOFFER", "android")).toMatchSnapshot(
"offer-code"
);
});
test("get invalid offer code", async () => {
const offers = new Offers();
hosts.SUBSCRIPTIONS_HOST = "https://subscriptions.streetwriters.co";
await expect(() => offers.getCode("INVALIDOFFER", "android")).rejects.toThrow(
/Not found/i
);
});

View File

@@ -7,18 +7,10 @@ const user = {
hashedPassword: process.env.HASHED_PASSWORD, hashedPassword: process.env.HASHED_PASSWORD,
}; };
test.skip("refresh token concurrently", async () => { test("refresh token concurrently", async () => {
const db = new DB(StorageInterface); const db = new DB(StorageInterface);
await db.init(); await db.init();
db.host({
API_HOST: "http://192.168.10.29:5264",
AUTH_HOST: "http://192.168.10.29:8264",
SSE_HOST: "http://192.168.10.29:7264",
SUBSCRIPTIONS_HOST: "http://192.168.10.29:9264",
ISSUES_HOST: "http://192.168.10.29:2624",
});
await expect( await expect(
db.user.login(user.email, user.password, user.hashedPassword) db.user.login(user.email, user.password, user.hashedPassword)
).resolves.not.toThrow(); ).resolves.not.toThrow();
@@ -36,18 +28,10 @@ test.skip("refresh token concurrently", async () => {
).toHaveLength(4); ).toHaveLength(4);
}, 30000); }, 30000);
test.skip("refresh token using the same refresh_token multiple time", async () => { test("refresh token using the same refresh_token multiple time", async () => {
const db = new DB(StorageInterface); const db = new DB(StorageInterface);
await db.init(); await db.init();
db.host({
API_HOST: "http://192.168.10.29:5264",
AUTH_HOST: "http://192.168.10.29:8264",
SSE_HOST: "http://192.168.10.29:7264",
SUBSCRIPTIONS_HOST: "http://192.168.10.29:9264",
ISSUES_HOST: "http://192.168.10.29:2624",
});
await expect( await expect(
db.user.login(user.email, user.password, user.hashedPassword) db.user.login(user.email, user.password, user.hashedPassword)
).resolves.not.toThrow(); ).resolves.not.toThrow();

View File

@@ -0,0 +1,72 @@
import { databaseTest } from "../__tests__/utils";
const user = {
email: process.env.EMAIL,
password: process.env.PASSWORD,
hashed: process.env.HASHED_PASSWORD,
};
// test("signup user and check for token", async () => {
// const db = new DB(StorageInterface);
// const usermanager = new UserManager(db);
// await expect(
// usermanager.signup(user.email, user.password)
// ).resolves.not.toThrow();
// await expect(usermanager.tokenManager.getToken()).resolves.toBeDefined();
// }, 30000);
test(
"login user and check for token",
() =>
databaseTest().then(async (db) => {
await expect(
db.user.login(user.email, user.password, user.hashed)
).resolves.not.toThrow();
await expect(db.user.tokenManager.getToken()).resolves.toBeDefined();
}),
30000
);
test(
"login user and get user data",
() =>
databaseTest().then(async (db) => {
await db.user.login(user.email, user.password, user.hashed);
const userData = await db.user.getUser();
expect(userData.email).toBe(user.email);
}),
30000
);
test(
"login user and logout user",
() =>
databaseTest().then(async (db) => {
await db.user.login(user.email, user.password, user.hashed);
await expect(db.user.logout()).resolves.not.toThrow();
}),
30000
);
// test("login user and delete user", async () => {
// const db = new DB(StorageInterface);
// const usermanager = new UserManager(db);
// await usermanager.login(user.email, user.password, user.hashed);
// await expect(usermanager.deleteUser(user.password)).resolves.toBe(true);
// }, 30000);
// test("login user and get user sessions", async () => {
// const db = new DB(StorageInterface);
// const usermanager = new UserManager(db);
// await usermanager.login(user.email, user.password);
// await usermanager.getSessions();
// }, 30000);

View File

@@ -1,67 +0,0 @@
import UserManager from "../api/user-manager";
import DB from "../api";
import StorageInterface from "../__mocks__/storage.mock";
const user = {
email: process.env.EMAIL,
password: process.env.PASSWORD,
};
test.skip("signup user and check for token", async () => {
const db = new DB(StorageInterface);
const usermanager = new UserManager(db);
await expect(
usermanager.signup(user.email, user.password)
).resolves.not.toThrow();
await expect(usermanager.tokenManager.getToken()).resolves.toBeDefined();
}, 30000);
test.skip("login user and check for token", async () => {
const db = new DB(StorageInterface);
const usermanager = new UserManager(db);
await expect(
usermanager.login(user.email, user.password)
).resolves.not.toThrow();
await expect(usermanager.tokenManager.getToken()).resolves.toBeDefined();
}, 30000);
test.skip("login user and get user data", async () => {
const db = new DB(StorageInterface);
const usermanager = new UserManager(db);
await usermanager.login(user.email, user.password);
const userData = await usermanager.getUser();
expect(userData.email).toBe(process.env.EMAIL);
}, 30000);
test.skip("login user and logout user", async () => {
const db = new DB(StorageInterface);
const usermanager = new UserManager(db);
await usermanager.login(user.email, user.password);
await expect(usermanager.logout()).resolves.not.toThrow();
}, 30000);
test.skip("login user and delete user", async () => {
const db = new DB(StorageInterface);
const usermanager = new UserManager(db);
await usermanager.login(user.email, user.password);
await expect(usermanager.deleteUser(user.password)).resolves.toBe(true);
}, 30000);
test("login user and get user sessions", async () => {
const db = new DB(StorageInterface);
const usermanager = new UserManager(db);
await usermanager.login(user.email, user.password);
await usermanager.getSessions();
}, 30000);

View File

@@ -1,4 +1,8 @@
import { databaseTest } from "./utils"; import { databaseTest, notebookTest, StorageInterface } from "./utils";
beforeEach(() => {
StorageInterface.clear();
});
test("settings' dateModified should not update on init", () => test("settings' dateModified should not update on init", () =>
databaseTest().then(async (db) => { databaseTest().then(async (db) => {
@@ -29,3 +33,75 @@ test("tag alias should update if aliases in settings update", () =>
}); });
expect(db.tags.tag(tag.id).alias).toBe("hello232"); expect(db.tags.tag(tag.id).alias).toBe("hello232");
})); }));
test("save group options", () =>
databaseTest().then(async (db) => {
const groupOptions = {
groupBy: "abc",
sortBy: "dateCreated",
sortDirection: "asc",
};
await db.settings.setGroupOptions("home", groupOptions);
expect(db.settings.getGroupOptions("home")).toMatchObject(groupOptions);
}));
test("save toolbar config", () =>
databaseTest().then(async (db) => {
const toolbarConfig = {
preset: "custom",
config: ["bold", "italic"],
};
await db.settings.setToolbarConfig("mobile", toolbarConfig);
expect(db.settings.getToolbarConfig("mobile")).toMatchObject(toolbarConfig);
}));
test("pinning an invalid item should throw", () =>
databaseTest().then(async (db) => {
await expect(() => db.settings.pin("lolo", {})).rejects.toThrow(
/item cannot be pinned/i
);
}));
test("pin a notebook", () =>
notebookTest().then(async ({ db, id }) => {
await db.settings.pin("notebook", { id });
expect(db.settings.pins).toHaveLength(1);
expect(db.settings.pins[0].id).toBe(id);
}));
test("pin an already pinned notebook", () =>
notebookTest().then(async ({ db, id }) => {
await db.settings.pin("notebook", { id });
await db.settings.pin("notebook", { id });
expect(db.settings.pins).toHaveLength(1);
expect(db.settings.pins[0].id).toBe(id);
}));
test("pin a topic", () =>
notebookTest().then(async ({ db, id }) => {
const notebook = db.notebooks.notebook(id)._notebook;
const topic = notebook.topics[0];
await db.settings.pin("topic", { id: topic.id, notebookId: id });
expect(db.settings.pins).toHaveLength(1);
expect(db.settings.pins[0].id).toBe(topic.id);
}));
test("pin a tag", () =>
databaseTest().then(async (db) => {
const tag = await db.tags.add("HELLO!");
await db.settings.pin("tag", { id: tag.id });
expect(db.settings.pins).toHaveLength(1);
expect(db.settings.pins[0].id).toBe(tag.id);
}));
test("unpin a pinned item", () =>
databaseTest().then(async (db) => {
const tag = await db.tags.add("HELLO!");
await db.settings.pin("tag", { id: tag.id });
expect(db.settings.pins).toHaveLength(1);
expect(db.settings.pins[0].id).toBe(tag.id);
await db.settings.unpin(tag.id);
expect(db.settings.pins).toHaveLength(0);
}));

View File

@@ -1,148 +0,0 @@
import { enableFetchMocks } from "jest-fetch-mock";
import { StorageInterface, databaseTest } from "./utils";
const SUCCESS_LOGIN_RESPONSE = {
access_token: "access_token",
refresh_token: "refresh_token",
scope: "sync",
expires_in: 3600,
};
const SUCCESS_USER_RESPONSE = {
id: "0",
email: process.env.EMAIL,
salt: "",
};
function mock(expiry = 3600) {
fetch
.mockResponseOnce(
JSON.stringify({ ...SUCCESS_LOGIN_RESPONSE, expires_in: expiry }),
{
headers: { "Content-Type": "application/json" },
}
)
.mockResponseOnce(JSON.stringify(SUCCESS_USER_RESPONSE), {
headers: { "Content-Type": "application/json" },
});
}
beforeAll(() => {
enableFetchMocks();
});
beforeEach(() => {
fetch.resetMocks();
StorageInterface.clear();
});
test("no user should be returned when not logged in", () =>
databaseTest().then(async (db) => {
expect(await db.user.getUser()).toBeUndefined();
}));
test("undefined user should not be set", () =>
databaseTest().then(async (db) => {
await db.user.setUser();
expect(await db.user.getUser()).toBeUndefined();
}));
test("login user", async () =>
databaseTest().then(async (db) => {
mock();
await db.user.login("myuser", "mylogin", true, "mylogin");
const dbuser = await db.user.getUser();
expect(dbuser.email).toBe(SUCCESS_USER_RESPONSE.email);
expect(dbuser.id).toBe(SUCCESS_USER_RESPONSE.id);
}));
test("login user with wrong password", () =>
databaseTest().then(async (db) => {
fetch.mockResponseOnce(
JSON.stringify({
error_description: "Username or password is incorrect.",
}),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
await expect(
db.user.login("myuser", "wrongpassword", true, "wrongpassword")
).rejects.toThrow(/Username or password is incorrect./);
}));
test("failed login with unknown error", () =>
databaseTest().then(async (db) => {
fetch.mockResponseOnce(JSON.stringify({}), { status: 400 });
await expect(
db.user.login("myuser", "wrongpassword", true, "wrongpassword")
).rejects.toThrow(/Request failed with status code: /);
}));
test("signup user", () =>
databaseTest().then(async (db) => {
fetch.mockResponseOnce(undefined, { status: 200 });
mock();
await db.user.signup(SUCCESS_USER_RESPONSE.email, "password");
const dbuser = await db.user.getUser();
expect(dbuser.email).toBe(SUCCESS_USER_RESPONSE.email);
}));
test("logout user", () =>
databaseTest().then(async (db) => {
mock();
await db.user.login("myuser", "mylogin", true, "mylogin");
const dbuser = await db.user.getUser();
expect(dbuser.email).toBe(SUCCESS_USER_RESPONSE.email);
await db.user.logout();
expect(await db.user.getUser()).toBeUndefined();
}));
test("refresh user's token", () =>
databaseTest().then(async (db) => {
mock();
await db.user.login(
SUCCESS_USER_RESPONSE.email,
"mylogin",
true,
"mylogin"
);
const token = await db.user.tokenManager.getToken();
await db.user.tokenManager.saveToken({ ...token, expires_in: -2000 });
fetch.mockResponseOnce(
JSON.stringify({
...SUCCESS_LOGIN_RESPONSE,
access_token: "new_token",
refresh_token: "new_refresh_token",
expires_in: 3600,
}),
{
headers: { "Content-Type": "application/json" },
}
);
const { access_token, refresh_token } =
await db.user.tokenManager.getToken();
expect(refresh_token).toBe("new_refresh_token");
expect(access_token).toBe("new_token");
}));
test("refresh user's token when its not expired", () =>
databaseTest().then(async (db) => {
mock();
await db.user.login(
SUCCESS_USER_RESPONSE.email,
"mylogin",
true,
"mylogin"
);
expect(await db.user.tokenManager.getAccessToken()).toBe("access_token");
const dbuser = await db.user.getUser();
expect(dbuser.email).toBe(SUCCESS_USER_RESPONSE.email);
}));
test("refresh token for non existent user should do nothing", () =>
databaseTest().then(async (db) => {
fetch.mockResponseOnce(JSON.stringify(SUCCESS_LOGIN_RESPONSE));
expect(await db.user.tokenManager.getAccessToken()).toBeUndefined();
const dbuser = await db.user.getUser();
expect(dbuser).toBeUndefined();
}));

View File

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`strip note with content: stripped-note-with-content 1`] = `"{\\"title\\":true,\\"description\\":false,\\"headline\\":true,\\"colored\\":false,\\"type\\":\\"note\\",\\"tags\\":[],\\"id\\":\\"hello\\",\\"contentId\\":\\"hello2\\",\\"dateModified\\":123,\\"dateEdited\\":123,\\"dateCreated\\":123,\\"additionalData\\":{\\"content\\":\\"{\\\\\\"title\\\\\\":false,\\\\\\"description\\\\\\":false,\\\\\\"headline\\\\\\":false,\\\\\\"colored\\\\\\":false,\\\\\\"type\\\\\\":\\\\\\"tiptap\\\\\\",\\\\\\"id\\\\\\":\\\\\\"hello\\\\\\",\\\\\\"dateModified\\\\\\":123,\\\\\\"dateEdited\\\\\\":123,\\\\\\"dateCreated\\\\\\":123}\\"}}"`;
exports[`strip note: stripped-note 1`] = `"{\\"title\\":true,\\"description\\":false,\\"headline\\":true,\\"colored\\":false,\\"type\\":\\"note\\",\\"tags\\":[],\\"id\\":\\"hello\\",\\"contentId\\":\\"hello2\\",\\"dateModified\\":123,\\"dateEdited\\":123,\\"dateCreated\\":123}"`;
exports[`strip notebook: stripped-notebook 1`] = `"{\\"title\\":true,\\"description\\":true,\\"headline\\":false,\\"colored\\":false,\\"type\\":\\"notebook\\",\\"id\\":\\"hello\\",\\"dateModified\\":123,\\"dateEdited\\":123,\\"dateCreated\\":123,\\"additionalData\\":[{\\"type\\":\\"topic\\",\\"id\\":\\"hello\\",\\"notebookId\\":\\"hello23\\",\\"title\\":\\"hello\\",\\"dateCreated\\":123,\\"dateEdited\\":123,\\"notes\\":[],\\"dateModified\\":123}]}"`;
exports[`strip tag: stripped-tag 1`] = `"{\\"title\\":true,\\"description\\":false,\\"headline\\":false,\\"colored\\":false,\\"type\\":\\"tag\\",\\"noteIds\\":[],\\"id\\":\\"hello\\",\\"dateModified\\":123,\\"dateEdited\\":123,\\"dateCreated\\":123}"`;
exports[`strip topic: stripped-topic 1`] = `"{\\"title\\":true,\\"description\\":false,\\"headline\\":false,\\"colored\\":false,\\"type\\":\\"topic\\",\\"notes\\":[],\\"id\\":\\"hello\\",\\"dateModified\\":123,\\"dateEdited\\":123,\\"dateCreated\\":123}"`;
exports[`strip trashed note: stripped-trashed-note 1`] = `"{\\"title\\":true,\\"description\\":false,\\"headline\\":true,\\"colored\\":false,\\"type\\":\\"trash\\",\\"tags\\":[],\\"id\\":\\"hello\\",\\"contentId\\":\\"hello2\\",\\"dateModified\\":123,\\"dateEdited\\":123,\\"dateDeleted\\":123,\\"dateCreated\\":123}"`;

View File

@@ -0,0 +1,82 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`get android pricing tier get monthly android tier: monthly-android-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 78,
"sku": "com.streetwriters.notesnook.sub.mo.tier3",
}
`;
exports[`get android pricing tier get yearly android tier: yearly-android-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 80,
"sku": "com.streetwriters.notesnook.sub.yr.tier3",
}
`;
exports[`get ios pricing tier get monthly ios tier: monthly-ios-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 78,
"sku": "com.streetwriters.notesnook.sub.mo.tier3",
}
`;
exports[`get ios pricing tier get yearly ios tier: yearly-ios-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 80,
"sku": "com.streetwriters.notesnook.sub.yr.tier3",
}
`;
exports[`get monthly price: monthly-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 78,
"price": 0.99,
}
`;
exports[`get undefined price: monthly-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 78,
"price": 0.99,
}
`;
exports[`get web pricing tier get monthly web tier: monthly-web-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 78,
"sku": "763943",
}
`;
exports[`get web pricing tier get yearly web tier: yearly-web-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 80,
"sku": "763945",
}
`;
exports[`get yearly price: yearly-pricing 1`] = `
Object {
"country": "Pakistan",
"countryCode": "PK",
"discount": 80,
"price": 9.99,
}
`;

View File

@@ -0,0 +1,125 @@
import Debug from "../debug";
import { noteTest, notebookTest, databaseTest } from "../../__tests__/utils";
import { enableFetchMocks, disableFetchMocks } from "jest-fetch-mock";
test("strip empty item shouldn't throw", () => {
const debug = new Debug();
expect(debug.strip()).toBe("{}");
});
test("strip note", () =>
noteTest().then(({ db, id }) => {
const note = db.notes.note(id)._note;
const debug = new Debug();
expect(debug.strip(normalizeItem(note))).toMatchSnapshot("stripped-note");
}));
test("strip trashed note", () =>
noteTest().then(async ({ db, id }) => {
await db.notes.delete(id);
const note = db.trash.all[0];
const debug = new Debug();
expect(debug.strip(normalizeItem(note))).toMatchSnapshot(
"stripped-trashed-note"
);
}));
test("strip note with content", () =>
noteTest().then(async ({ db, id }) => {
const note = db.notes.note(id)._note;
const debug = new Debug();
const content = await db.content.raw(note.contentId);
note.additionalData = {
content: db.debug.strip(normalizeItem(content)),
};
expect(debug.strip(normalizeItem(note))).toMatchSnapshot(
"stripped-note-with-content"
);
}));
test("strip notebook", () =>
notebookTest().then(async ({ db, id }) => {
const notebook = db.notebooks.notebook(id)._notebook;
const debug = new Debug();
notebook.additionalData = notebook.topics.map((topic) =>
normalizeItem(topic)
);
expect(debug.strip(normalizeItem(notebook))).toMatchSnapshot(
"stripped-notebook"
);
}));
test("strip topic", () =>
notebookTest().then(async ({ db, id }) => {
const notebook = db.notebooks.notebook(id)._notebook;
const debug = new Debug();
expect(debug.strip(normalizeItem(notebook.topics[0]))).toMatchSnapshot(
"stripped-topic"
);
}));
test("strip tag", () =>
databaseTest().then(async (db) => {
const tag = await db.tags.add("Hello tag");
const debug = new Debug();
expect(debug.strip(normalizeItem(tag))).toMatchSnapshot("stripped-tag");
}));
test("reporting empty issue should return undefined", async () => {
const debug = new Debug();
expect(await debug.report()).toBeUndefined();
});
const SUCCESS_REPORT_RESPONSE = {
url: "https://reported/",
};
test("reporting issue should return issue url", async () => {
enableFetchMocks();
const debug = new Debug();
fetch.mockResponseOnce(JSON.stringify(SUCCESS_REPORT_RESPONSE), {
headers: { "Content-Type": "application/json" },
});
expect(
await debug.report({
title: "I am title",
body: "I am body",
userId: "anything",
})
).toBe(SUCCESS_REPORT_RESPONSE.url);
disableFetchMocks();
});
test("reporting invalid issue should return undefined", async () => {
enableFetchMocks();
const debug = new Debug();
fetch.mockResponseOnce(
JSON.stringify({
error_description: "Invalid issue.",
}),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
expect(await debug.report({})).toBeUndefined();
disableFetchMocks();
});
function normalizeItem(item) {
item.id = "hello";
item.notebookId = "hello23";
item.dateModified = 123;
item.dateEdited = 123;
item.dateCreated = 123;
if (item.dateDeleted) item.dateDeleted = 123;
if (item.contentId) item.contentId = "hello2";
return item;
}

View File

@@ -0,0 +1 @@
test.todo("skipped test");

View File

@@ -0,0 +1 @@
test.todo("skipped test");

View File

@@ -0,0 +1 @@
test.todo("skipped test");

View File

@@ -0,0 +1,18 @@
import Pricing from "../pricing";
test.each(["monthly", "yearly", undefined])(`get %s price`, async (period) => {
const pricing = new Pricing();
const price = await pricing.price(period);
expect(price).toMatchSnapshot(`${period || "monthly"}-pricing`);
});
describe.each(["android", "ios", "web"])(`get %s pricing tier`, (platform) => {
test.each(["monthly", "yearly"])(
`get %s ${platform} tier`,
async (period) => {
const pricing = new Pricing();
const price = await pricing.sku(platform, period);
expect(price).toMatchSnapshot(`${period}-${platform}-pricing`);
}
);
});

View File

@@ -2,6 +2,7 @@ import hosts from "../utils/constants";
export default class Debug { export default class Debug {
strip(item) { strip(item) {
if (!item) return "{}";
return JSON.stringify({ return JSON.stringify({
title: !!item.title, title: !!item.title,
description: !!item.description, description: !!item.description,
@@ -9,6 +10,8 @@ export default class Debug {
colored: !!item.color, colored: !!item.color,
type: item.type, type: item.type,
notebooks: item.notebooks, notebooks: item.notebooks,
notes: item.notes,
noteIds: item.noteIds,
tags: item.tags, tags: item.tags,
id: item.id, id: item.id,
contentId: item.contentId, contentId: item.contentId,

View File

@@ -8,11 +8,11 @@ class Monographs {
*/ */
constructor(db) { constructor(db) {
this._db = db; this._db = db;
this.monographs = []; this.monographs = undefined;
} }
async deinit() { async deinit() {
this.monographs = []; this.monographs = undefined;
await this._db.storage.write("monographs", this.monographs); await this._db.storage.write("monographs", this.monographs);
} }
@@ -37,7 +37,7 @@ class Monographs {
* @returns {boolean} Whether note is published or not. * @returns {boolean} Whether note is published or not.
*/ */
isPublished(noteId) { isPublished(noteId) {
return this.monographs.indexOf(noteId) > -1; return this.monographs && this.monographs.indexOf(noteId) > -1;
} }
/** /**
@@ -46,6 +46,8 @@ class Monographs {
* @returns Monograph Id * @returns Monograph Id
*/ */
monograph(noteId) { monograph(noteId) {
if (!this.monographs) return;
return this.monographs[this.monographs.indexOf(noteId)]; return this.monographs[this.monographs.indexOf(noteId)];
} }
@@ -55,7 +57,7 @@ class Monographs {
* @param {{password: string, selfDestruct: boolean}} opts Publish options * @param {{password: string, selfDestruct: boolean}} opts Publish options
* @returns * @returns
*/ */
async publish(noteId, opts) { async publish(noteId, opts = { password: undefined, selfDestruct: false }) {
if (!this.monographs) await this.init(); if (!this.monographs) await this.init();
let update = !!this.isPublished(noteId); let update = !!this.isPublished(noteId);
@@ -116,8 +118,8 @@ class Monographs {
const token = await this._db.user.tokenManager.getAccessToken(); const token = await this._db.user.tokenManager.getAccessToken();
if (!user || !token) throw new Error("Please login to publish a note."); if (!user || !token) throw new Error("Please login to publish a note.");
const note = this._db.notes.note(noteId); // const note = this._db.notes.note(noteId);
if (!note) throw new Error("No such note found."); // if (!note) throw new Error("No such note found.");
if (!this.isPublished(noteId)) if (!this.isPublished(noteId))
throw new Error("This note is not published."); throw new Error("This note is not published.");
@@ -128,9 +130,15 @@ class Monographs {
} }
get all() { get all() {
if (!this.monographs) return [];
return this._db.notes.all.filter( return this._db.notes.all.filter(
(note) => this.monographs.indexOf(note.id) > -1 (note) => this.monographs.indexOf(note.id) > -1
); );
} }
async get(monographId) {
return await http.get(`${Constants.API_HOST}/monographs/${monographId}`);
}
} }
export default Monographs; export default Monographs;

View File

@@ -27,7 +27,7 @@ class Pricing {
* discount: number * discount: number
* }>} * }>}
*/ */
price(period) { price(period = "monthly") {
return http.get(`${BASE_URL}/${period}`); return http.get(`${BASE_URL}/${period}`);
} }
} }

View File

@@ -130,7 +130,7 @@ class Settings {
} }
isPinned(id) { isPinned(id) {
return this._settings.pins.findIndex((v) => v.data.id === id) > -1; return !!this._settings.pins.find((v) => v.data.id === id);
} }
get pins() { get pins() {

View File

@@ -1,421 +0,0 @@
import Database from "../../index";
import { NodeStorageInterface } from "../../../__mocks__/node-storage.mock";
import FS from "../../../__mocks__/fs.mock";
import { CHECK_IDS, EV, EVENTS } from "../../../common";
import EventSource from "eventsource";
import { delay } from "../../../__tests__/utils";
test.skip(
"case 1: device A & B should only download the changes from device C (no uploading)",
async () => {
const types = [];
function onSyncProgress({ type }) {
types.push(type);
}
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
deviceA.eventManager.subscribe(EVENTS.syncProgress, onSyncProgress);
deviceB.eventManager.subscribe(EVENTS.syncProgress, onSyncProgress);
const deviceC = await initializeDevice("deviceC");
await deviceC.notes.add({ title: "new note 1" });
await syncAndWait(deviceC, deviceC);
expect(types.every((t) => t === "download")).toBe(true);
await cleanup(deviceA, deviceB, deviceC);
},
600 * 1000
);
test.skip(
"case 3: Device A & B have unsynced changes but server has nothing",
async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
const note1Id = await deviceA.notes.add({
title: "Test note from device A",
});
const note2Id = await deviceB.notes.add({
title: "Test note from device B",
});
await syncAndWait(deviceA, deviceB);
expect(deviceA.notes.note(note2Id)).toBeTruthy();
expect(deviceB.notes.note(note1Id)).toBeTruthy();
expect(deviceA.notes.note(note1Id)).toBeTruthy();
expect(deviceB.notes.note(note2Id)).toBeTruthy();
await cleanup(deviceA, deviceA);
},
30 * 1000
);
// test.skip(
// "case 4: Device A's sync is interrupted halfway and Device B makes some changes afterwards and syncs.",
// async () => {
// const deviceA = await initializeDevice("deviceA");
// const deviceB = await initializeDevice("deviceB");
// const unsyncedNoteIds = [];
// for (let i = 0; i < 10; ++i) {
// const id = await deviceA.notes.add({
// title: `Test note ${i} from device A`,
// });
// unsyncedNoteIds.push(id);
// }
// const half = unsyncedNoteIds.length / 2 + 1;
// deviceA.eventManager.subscribe(
// EVENTS.syncProgress,
// async ({ type, current }) => {
// if (type === "upload" && current === half) {
// await deviceA.syncer.stop();
// }
// }
// );
// await expect(deviceA.sync(true)).rejects.toThrow();
// let syncedNoteIds = [];
// for (let i = 0; i < unsyncedNoteIds.length; ++i) {
// const expectedNoteId = unsyncedNoteIds[i];
// if (deviceB.notes.note(expectedNoteId))
// syncedNoteIds.push(expectedNoteId);
// }
// expect(
// syncedNoteIds.length === half - 1 || syncedNoteIds.length === half
// ).toBe(true);
// const deviceBNoteId = await deviceB.notes.add({
// title: "Test note of case 4 from device B",
// });
// await deviceB.sync(true);
// await syncAndWait(deviceA, deviceB);
// expect(deviceA.notes.note(deviceBNoteId)).toBeTruthy();
// expect(
// unsyncedNoteIds
// .map((id) => !!deviceB.notes.note(id))
// .every((res) => res === true)
// ).toBe(true);
// await cleanup(deviceA, deviceB);
// },
// 60 * 1000
// );
// test.only(
// "case 5: Device A's sync is interrupted halfway and Device B makes changes on the same note's content that didn't get synced on Device A due to interruption.",
// async () => {
// const deviceA = await initializeDevice("deviceA");
// const deviceB = await initializeDevice("deviceB");
// const noteIds = [];
// for (let i = 0; i < 10; ++i) {
// const id = await deviceA.notes.add({
// content: {
// type: "tiptap",
// data: `<p>deviceA=true</p>`,
// },
// });
// noteIds.push(id);
// }
// await deviceA.sync(true);
// await deviceB.sync(true);
// const unsyncedNoteIds = [];
// for (let id of noteIds) {
// const noteId = await deviceA.notes.add({
// id,
// content: {
// type: "tiptap",
// data: `<p>deviceA=true+changed=true</p>`,
// },
// });
// unsyncedNoteIds.push(noteId);
// }
// deviceA.eventManager.subscribe(
// EVENTS.syncProgress,
// async ({ type, total, current }) => {
// const half = total / 2 + 1;
// if (type === "upload" && current === half) {
// await deviceA.syncer.stop();
// }
// }
// );
// await expect(deviceA.sync(true)).rejects.toThrow();
// await delay(10 * 1000);
// for (let id of unsyncedNoteIds) {
// await deviceB.notes.add({
// id,
// content: {
// type: "tiptap",
// data: "<p>changes from device B</p>",
// },
// });
// }
// const error = await withError(async () => {
// await deviceB.sync(true);
// await deviceA.sync(true);
// });
// expect(error).not.toBeInstanceOf(NoErrorThrownError);
// expect(error.message.includes("Merge")).toBeTruthy();
// await cleanup(deviceA, deviceB);
// },
// 60 * 1000
// );
test.skip(
"issue: running force sync from device A makes device B always download everything",
async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
await syncAndWait(deviceA, deviceB, true);
const handler = jest.fn();
deviceB.eventManager.subscribe(EVENTS.syncProgress, handler);
await deviceB.sync(true);
expect(handler).not.toHaveBeenCalled();
await cleanup(deviceB);
},
60 * 1000
);
test.skip(
"issue: colors are not properly created if multiple notes are synced together",
async () => {
const deviceA = await initializeDevice("deviceA", [CHECK_IDS.noteColor]);
const deviceB = await initializeDevice("deviceB", [CHECK_IDS.noteColor]);
const noteIds = [];
for (let i = 0; i < 3; ++i) {
const id = await deviceA.notes.add({
content: {
type: "tiptap",
data: `<p>deviceA=true</p>`,
},
});
noteIds.push(id);
}
await syncAndWait(deviceA, deviceB);
for (let noteId of noteIds) {
await deviceA.notes.note(noteId).color("purple");
expect(deviceB.notes.note(noteId)).toBeTruthy();
expect(deviceB.notes.note(noteId).data.color).toBeUndefined();
}
await syncAndWait(deviceA, deviceB);
await delay(2000);
const purpleColor = deviceB.colors.tag("purple");
console.log(noteIds, purpleColor.noteIds);
expect(noteIds.every((id) => purpleColor.noteIds.indexOf(id) > -1)).toBe(
true
);
await cleanup(deviceA, deviceB);
},
60 * 1000
);
test.skip(
"issue: new topic on device A gets replaced by the new topic on device B",
async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
const id = await deviceA.notebooks.add({ title: "Notebook 1" });
await syncAndWait(deviceA, deviceB, false);
expect(deviceB.notebooks.notebook(id)).toBeDefined();
await deviceA.notebooks.notebook(id).topics.add("Topic 1");
// to create a conflict
await delay(1500);
await deviceB.notebooks.notebook(id).topics.add("Topic 2");
expect(deviceA.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy();
expect(deviceB.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy();
await syncAndWait(deviceA, deviceB, false);
await delay(1000);
await syncAndWait(deviceB, deviceB, false);
expect(deviceA.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy();
expect(deviceB.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy();
expect(deviceA.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy();
expect(deviceB.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy();
await cleanup(deviceA, deviceB);
},
60 * 1000
);
test.skip(
"issue: remove notebook reference from notes that are removed from topic during merge",
async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
const id = await deviceA.notebooks.add({
title: "Notebook 1",
topics: ["Topic 1"],
});
await syncAndWait(deviceA, deviceB, false);
expect(deviceB.notebooks.notebook(id)).toBeDefined();
const noteA = await deviceA.notes.add({ title: "Note 1" });
await deviceA.notes.move({ id, topic: "Topic 1" }, noteA);
expect(
deviceA.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
await delay(2000);
const noteB = await deviceB.notes.add({ title: "Note 2" });
await deviceB.notes.move({ id, topic: "Topic 1" }, noteB);
expect(
deviceB.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
await syncAndWait(deviceB, deviceA, false);
expect(
deviceA.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
expect(
deviceB.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
expect(deviceA.notes.note(noteA).data.notebooks).toHaveLength(0);
await cleanup(deviceA, deviceB);
},
60 * 1000
);
/**
*
* @param {string} id
* @returns {Promise<Database>}
*/
async function initializeDevice(id, capabilities = []) {
EV.subscribe(EVENTS.userCheckStatus, async (type) => {
return {
type,
result: capabilities.indexOf(type) > -1,
};
});
const device = new Database(new NodeStorageInterface(), EventSource, FS);
device.host({
API_HOST: "http://192.168.10.29:5264",
AUTH_HOST: "http://192.168.10.29:8264",
SSE_HOST: "http://192.168.10.29:7264",
ISSUES_HOST: "http://192.168.10.29:2624",
SUBSCRIPTIONS_HOST: "http://192.168.10.29:9264",
});
await device.init(id);
await device.user.login("enkaboot@gmail.com", process.env.PASSWORD);
await device.user.resetUser(false);
device.eventManager.subscribe(
EVENTS.databaseSyncRequested,
async (full, force) => {
await device.sync(full, force);
}
);
await syncAndWait(device, device);
return device;
}
async function cleanup(...devices) {
for (let device of devices) {
await device.user.logout();
device.eventManager.unsubscribeAll();
}
EV.unsubscribeAll();
}
/**
*
* @param {Database} device
* @returns
*/
// function waitForSyncCompleted(device) {
// return new Promise((resolve) =>
// device.eventManager.subscribe(EVENTS.syncCompleted, () => {
// resolve();
// })
// );
// }
/**
*
* @param {Database} deviceA
* @param {Database} deviceB
* @returns
*/
function syncAndWait(deviceA, deviceB, force = false) {
return new Promise((resolve) => {
const ref = deviceB.eventManager.subscribe(EVENTS.syncCompleted, () => {
ref.unsubscribe();
resolve();
});
deviceA.sync(true, force);
});
}
class NoErrorThrownError extends Error {}
/**
*
* @param {Function} call
* @returns {Promise<Error>}
*/
async function withError(call) {
try {
await call();
throw new NoErrorThrownError();
} catch (error) {
return error;
}
}

View File

@@ -0,0 +1,375 @@
import Database from "../../index";
import { NodeStorageInterface } from "../../../__mocks__/node-storage.mock";
import FS from "../../../__mocks__/fs.mock";
import { CHECK_IDS, EV, EVENTS } from "../../../common";
import EventSource from "eventsource";
import { delay } from "../../../__tests__/utils";
jest.setTimeout(100 * 1000);
test("case 1: device A & B should only download the changes from device C (no uploading)", async () => {
const types = [];
function onSyncProgress({ type }) {
types.push(type);
}
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
deviceA.eventManager.subscribe(EVENTS.syncProgress, onSyncProgress);
deviceB.eventManager.subscribe(EVENTS.syncProgress, onSyncProgress);
const deviceC = await initializeDevice("deviceC");
await deviceC.notes.add({ title: "new note 1" });
await syncAndWait(deviceC, deviceC);
expect(types.every((t) => t === "download")).toBe(true);
await cleanup(deviceA, deviceB, deviceC);
});
test("case 3: Device A & B have unsynced changes but server has nothing", async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
const note1Id = await deviceA.notes.add({
title: "Test note from device A",
});
const note2Id = await deviceB.notes.add({
title: "Test note from device B",
});
await syncAndWait(deviceA, deviceB);
expect(deviceA.notes.note(note2Id)).toBeTruthy();
expect(deviceB.notes.note(note1Id)).toBeTruthy();
expect(deviceA.notes.note(note1Id)).toBeTruthy();
expect(deviceB.notes.note(note2Id)).toBeTruthy();
await cleanup(deviceA, deviceA);
});
// test(
// "case 4: Device A's sync is interrupted halfway and Device B makes some changes afterwards and syncs.",
// async () => {
// const deviceA = await initializeDevice("deviceA");
// const deviceB = await initializeDevice("deviceB");
// const unsyncedNoteIds = [];
// for (let i = 0; i < 10; ++i) {
// const id = await deviceA.notes.add({
// title: `Test note ${i} from device A`,
// });
// unsyncedNoteIds.push(id);
// }
// const half = unsyncedNoteIds.length / 2 + 1;
// deviceA.eventManager.subscribe(
// EVENTS.syncProgress,
// async ({ type, current }) => {
// if (type === "upload" && current === half) {
// await deviceA.syncer.stop();
// }
// }
// );
// await expect(deviceA.sync(true)).rejects.toThrow();
// let syncedNoteIds = [];
// for (let i = 0; i < unsyncedNoteIds.length; ++i) {
// const expectedNoteId = unsyncedNoteIds[i];
// if (deviceB.notes.note(expectedNoteId))
// syncedNoteIds.push(expectedNoteId);
// }
// expect(
// syncedNoteIds.length === half - 1 || syncedNoteIds.length === half
// ).toBe(true);
// const deviceBNoteId = await deviceB.notes.add({
// title: "Test note of case 4 from device B",
// });
// await deviceB.sync(true);
// await syncAndWait(deviceA, deviceB);
// expect(deviceA.notes.note(deviceBNoteId)).toBeTruthy();
// expect(
// unsyncedNoteIds
// .map((id) => !!deviceB.notes.note(id))
// .every((res) => res === true)
// ).toBe(true);
// await cleanup(deviceA, deviceB);
// },
//
// );
// test.only(
// "case 5: Device A's sync is interrupted halfway and Device B makes changes on the same note's content that didn't get synced on Device A due to interruption.",
// async () => {
// const deviceA = await initializeDevice("deviceA");
// const deviceB = await initializeDevice("deviceB");
// const noteIds = [];
// for (let i = 0; i < 10; ++i) {
// const id = await deviceA.notes.add({
// content: {
// type: "tiptap",
// data: `<p>deviceA=true</p>`,
// },
// });
// noteIds.push(id);
// }
// await deviceA.sync(true);
// await deviceB.sync(true);
// const unsyncedNoteIds = [];
// for (let id of noteIds) {
// const noteId = await deviceA.notes.add({
// id,
// content: {
// type: "tiptap",
// data: `<p>deviceA=true+changed=true</p>`,
// },
// });
// unsyncedNoteIds.push(noteId);
// }
// deviceA.eventManager.subscribe(
// EVENTS.syncProgress,
// async ({ type, total, current }) => {
// const half = total / 2 + 1;
// if (type === "upload" && current === half) {
// await deviceA.syncer.stop();
// }
// }
// );
// await expect(deviceA.sync(true)).rejects.toThrow();
// await delay(10 * 1000);
// for (let id of unsyncedNoteIds) {
// await deviceB.notes.add({
// id,
// content: {
// type: "tiptap",
// data: "<p>changes from device B</p>",
// },
// });
// }
// const error = await withError(async () => {
// await deviceB.sync(true);
// await deviceA.sync(true);
// });
// expect(error).not.toBeInstanceOf(NoErrorThrownError);
// expect(error.message.includes("Merge")).toBeTruthy();
// await cleanup(deviceA, deviceB);
// },
//
// );
test("issue: running force sync from device A makes device B always download everything", async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
await syncAndWait(deviceA, deviceB, true);
const handler = jest.fn();
deviceB.eventManager.subscribe(EVENTS.syncProgress, handler);
await deviceB.sync(true);
expect(handler).not.toHaveBeenCalled();
await cleanup(deviceB);
});
test("issue: colors are not properly created if multiple notes are synced together", async () => {
const deviceA = await initializeDevice("deviceA", [CHECK_IDS.noteColor]);
const deviceB = await initializeDevice("deviceB", [CHECK_IDS.noteColor]);
const noteIds = [];
for (let i = 0; i < 3; ++i) {
const id = await deviceA.notes.add({
content: {
type: "tiptap",
data: `<p>deviceA=true</p>`,
},
});
noteIds.push(id);
}
await syncAndWait(deviceA, deviceB);
for (let noteId of noteIds) {
await deviceA.notes.note(noteId).color("purple");
expect(deviceB.notes.note(noteId)).toBeTruthy();
expect(deviceB.notes.note(noteId).data.color).toBeUndefined();
}
await syncAndWait(deviceA, deviceB);
await delay(2000);
const purpleColor = deviceB.colors.tag("purple");
console.log(noteIds, purpleColor.noteIds);
expect(noteIds.every((id) => purpleColor.noteIds.indexOf(id) > -1)).toBe(
true
);
await cleanup(deviceA, deviceB);
});
test("issue: new topic on device A gets replaced by the new topic on device B", async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
const id = await deviceA.notebooks.add({ title: "Notebook 1" });
await syncAndWait(deviceA, deviceB, false);
expect(deviceB.notebooks.notebook(id)).toBeDefined();
await deviceA.notebooks.notebook(id).topics.add("Topic 1");
// to create a conflict
await delay(1500);
await deviceB.notebooks.notebook(id).topics.add("Topic 2");
expect(deviceA.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy();
expect(deviceB.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy();
await syncAndWait(deviceA, deviceB, false);
await delay(1000);
await syncAndWait(deviceB, deviceB, false);
expect(deviceA.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy();
expect(deviceB.notebooks.notebook(id).topics.has("Topic 1")).toBeTruthy();
expect(deviceA.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy();
expect(deviceB.notebooks.notebook(id).topics.has("Topic 2")).toBeTruthy();
await cleanup(deviceA, deviceB);
});
test("issue: remove notebook reference from notes that are removed from topic during merge", async () => {
const deviceA = await initializeDevice("deviceA");
const deviceB = await initializeDevice("deviceB");
const id = await deviceA.notebooks.add({
title: "Notebook 1",
topics: ["Topic 1"],
});
await syncAndWait(deviceA, deviceB, false);
expect(deviceB.notebooks.notebook(id)).toBeDefined();
const noteA = await deviceA.notes.add({ title: "Note 1" });
await deviceA.notes.move({ id, topic: "Topic 1" }, noteA);
expect(
deviceA.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
await delay(2000);
const noteB = await deviceB.notes.add({ title: "Note 2" });
await deviceB.notes.move({ id, topic: "Topic 1" }, noteB);
expect(
deviceB.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
await syncAndWait(deviceB, deviceA, false);
expect(
deviceA.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
expect(
deviceB.notebooks.notebook(id).topics.topic("Topic 1").totalNotes
).toBe(1);
expect(deviceA.notes.note(noteA).data.notebooks).toHaveLength(0);
await cleanup(deviceA, deviceB);
});
/**
*
* @param {string} id
* @returns {Promise<Database>}
*/
async function initializeDevice(id, capabilities = []) {
console.time("Init device");
EV.subscribe(EVENTS.userCheckStatus, async (type) => {
return {
type,
result: capabilities.indexOf(type) > -1,
};
});
const device = new Database(new NodeStorageInterface(), EventSource, FS);
// device.host({
// API_HOST: "http://192.168.10.29:5264",
// AUTH_HOST: "http://192.168.10.29:8264",
// SSE_HOST: "http://192.168.10.29:7264",
// ISSUES_HOST: "http://192.168.10.29:2624",
// SUBSCRIPTIONS_HOST: "http://192.168.10.29:9264",
// });
await device.init(id);
await device.user.login(
process.env.EMAIL,
process.env.PASSWORD,
process.env.HASHED_PASSWORD
);
await device.user.resetUser(false);
device.eventManager.subscribe(
EVENTS.databaseSyncRequested,
async (full, force) => {
await device.sync(full, force);
}
);
console.timeEnd("Init device");
return device;
}
async function cleanup(...devices) {
for (let device of devices) {
await device.user.logout();
device.eventManager.unsubscribeAll();
}
EV.unsubscribeAll();
}
/**
*
* @param {Database} deviceA
* @param {Database} deviceB
* @returns
*/
function syncAndWait(deviceA, deviceB, force = false) {
return new Promise((resolve) => {
const ref = deviceB.eventManager.subscribe(EVENTS.syncCompleted, () => {
ref.unsubscribe();
resolve();
});
deviceA.sync(true, force);
});
}

View File

@@ -100,19 +100,5 @@ class Collector {
...(await this._serialize(item)), ...(await this._serialize(item)),
}; };
} }
filter(data, predicate) {
const arrays = ["notes", "notebooks", "content", "attachments", "settings"];
const newData = {};
for (let array of arrays) {
if (!data[array]) continue;
this.logger.info(`Filtering from ${data[array].length} ${array}`);
newData[array] = data[array].filter(predicate);
this.logger.info(
`Length after filtering ${array}: ${newData[array].length}`
);
}
return newData;
}
} }
export default Collector; export default Collector;

View File

@@ -2,7 +2,7 @@ import "../types";
import http from "../utils/http"; import http from "../utils/http";
import constants from "../utils/constants"; import constants from "../utils/constants";
import TokenManager from "./token-manager"; import TokenManager from "./token-manager";
import { EV, EVENTS, setUserPersonalizationBytes } from "../common"; import { EV, EVENTS } from "../common";
const ENDPOINTS = { const ENDPOINTS = {
signup: "/users", signup: "/users",
@@ -44,7 +44,6 @@ class UserManager {
async init() { async init() {
const user = await this.getUser(); const user = await this.getUser();
if (!user) return; if (!user) return;
setUserPersonalizationBytes(user.salt);
} }
async signup(email, password) { async signup(email, password) {
@@ -58,8 +57,8 @@ class UserManager {
return await this._login({ email, password, hashedPassword }); return await this._login({ email, password, hashedPassword });
} }
async login(email, password) { async login(email, password, hashedPassword = undefined) {
return this._login({ email, password }); return this._login({ email, password, hashedPassword });
} }
async mfaLogin(email, password, { code, method }) { async mfaLogin(email, password, { code, method }) {
@@ -87,7 +86,6 @@ class UserManager {
); );
const user = await this.fetchUser(); const user = await this.fetchUser();
setUserPersonalizationBytes(user.salt);
await this._storage.deriveCryptoKey(`_uk_@${user.email}`, { await this._storage.deriveCryptoKey(`_uk_@${user.email}`, {
password, password,
salt: user.salt, salt: user.salt,

View File

@@ -65,15 +65,3 @@ export const EVENTS = {
}; };
export const CURRENT_DATABASE_VERSION = 5.6; export const CURRENT_DATABASE_VERSION = 5.6;
export function setUserPersonalizationBytes(userSalt) {
USER_PERSONALIZATION_HASH = new Uint8Array(
Buffer.from(userSalt, "base64")
).slice(0, 8);
if (
!USER_PERSONALIZATION_HASH.length ||
!USER_PERSONALIZATION_HASH.byteLength
)
USER_PERSONALIZATION_HASH = undefined;
}
export var USER_PERSONALIZATION_HASH = null;

View File

@@ -1,26 +1,27 @@
import { extractHostname } from "./hostname"; import { extractHostname } from "./hostname";
function isProduction() {
return (
process.env.NODE_ENV === "production" || process.env.NODE_ENV === "test"
);
}
const hosts = { const hosts = {
API_HOST: API_HOST: isProduction()
process.env.NODE_ENV === "production" ? "https://api.notesnook.com"
? "https://api.notesnook.com" : "http://localhost:5264",
: "http://localhost:5264", AUTH_HOST: isProduction()
AUTH_HOST: ? "https://auth.streetwriters.co"
process.env.NODE_ENV === "production" : "http://localhost:8264",
? "https://auth.streetwriters.co" SSE_HOST: isProduction()
: "http://localhost:8264", ? "https://events.streetwriters.co"
SSE_HOST: : "http://localhost:7264",
process.env.NODE_ENV === "production" SUBSCRIPTIONS_HOST: isProduction()
? "https://events.streetwriters.co" ? "https://subscriptions.streetwriters.co"
: "http://localhost:7264", : "http://localhost:9264",
SUBSCRIPTIONS_HOST: ISSUES_HOST: isProduction()
process.env.NODE_ENV === "production" ? "https://issues.streetwriters.co"
? "https://subscriptions.streetwriters.co" : "http://localhost:2624",
: "http://localhost:9264",
ISSUES_HOST:
process.env.NODE_ENV === "production"
? "https://issues.streetwriters.co"
: "http://localhost:2624",
}; };
export default hosts; export default hosts;