diff --git a/app/lib/dal/drivers/LokiFsAdapter.ts b/app/lib/dal/drivers/LokiFsAdapter.ts index 4d278753e0dfbc251b79bace76e7de17f595ba3e..e7f5342cce566331d4db9be0fe4757f438a5dc52 100644 --- a/app/lib/dal/drivers/LokiFsAdapter.ts +++ b/app/lib/dal/drivers/LokiFsAdapter.ts @@ -58,6 +58,14 @@ export class LokiFsAdapter { return new Promise(res => loki.saveDatabaseInternal(res)) } + async listPendingChanges(): Promise<string[]> { + if (!(await this.cfs.exists(LokiFsAdapter.COMMIT_FILE))) { + return [] + } + const commitObj = await this.cfs.readJSON(LokiFsAdapter.COMMIT_FILE) + return commitObj.changes + } + /** * Flushes the DB changes to disk. * @param loki diff --git a/app/lib/dal/drivers/LokiJsDriver.ts b/app/lib/dal/drivers/LokiJsDriver.ts index b895cfefcd42d74e9efc1ff111ed8b00167c2403..7a3dfc549285da3a774f3fc942dab4f2d9da008f 100644 --- a/app/lib/dal/drivers/LokiJsDriver.ts +++ b/app/lib/dal/drivers/LokiJsDriver.ts @@ -35,4 +35,8 @@ export class LokiJsDriver { async flushAndTrimData() { return this.adapter.dbDump(this.lokiInstance) } + + async listChangesFilesPending(): Promise<string[]> { + return this.adapter.listPendingChanges() + } } diff --git a/app/lib/dal/indexDAL/loki/LokiCollection.ts b/app/lib/dal/indexDAL/loki/LokiCollection.ts index 04ee588780820faf55279e40d970dc141261e942..e160608b620f75c4858fa95c87990618b1ef985e 100644 --- a/app/lib/dal/indexDAL/loki/LokiCollection.ts +++ b/app/lib/dal/indexDAL/loki/LokiCollection.ts @@ -6,7 +6,7 @@ const logger = NewLogger() export class LokiProxyCollection<T> implements LokiCollection<T> { - constructor(private collection:LokiCollection<T>, private collectionName:string) { + constructor(public collection:LokiCollection<T>, private collectionName:string) { } get data() { @@ -41,4 +41,19 @@ export class LokiProxyCollection<T> implements LokiCollection<T> { // This is a Loki bug: `disableDeltaChangesApi` should be impacted just like `disableChangesApi`: ;(this.collection as any).disableDeltaChangesApi = !enabled } + + // Returns the real Loki property + get disableChangesApi() { + return this.collection.disableChangesApi + } + + // Returns the real Loki property + get disableDeltaChangesApi() { + return this.collection.disableDeltaChangesApi + } + + get changes() { + return this.collection.changes + } + } diff --git a/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts b/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts index fe0484af9afa9429a0a421622438fdd7edf894e9..ce6d2ad06d0fb67baf363f1df1b8b977e946a067 100755 --- a/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts +++ b/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts @@ -19,6 +19,10 @@ export abstract class LokiCollectionManager<T> implements LokiDAO { this.collectionIsInitialized = new Promise<void>(res => this.resolveCollection = res) } + get lokiCollection(): LokiCollection<T> { + return this.collection + } + public triggerInit() { const coll = this.loki.addCollection(this.collectionName, { indices: this.indices, diff --git a/app/lib/dal/indexDAL/loki/LokiDAO.ts b/app/lib/dal/indexDAL/loki/LokiDAO.ts index 9c0a9ffba823b5eae81b18b3f90d3d76c4c51c23..b060804d77b59d7bdc76ed8d2565bd73547471c8 100644 --- a/app/lib/dal/indexDAL/loki/LokiDAO.ts +++ b/app/lib/dal/indexDAL/loki/LokiDAO.ts @@ -1,7 +1,10 @@ +import {LokiCollection} from "./LokiTypes" export interface LokiDAO { enableChangesAPI(): void disableChangesAPI(): void + + lokiCollection: LokiCollection<any> } diff --git a/app/lib/dal/indexDAL/loki/LokiTypes.ts b/app/lib/dal/indexDAL/loki/LokiTypes.ts index ff34a77f47e034ccdee83ac46d01a97507545932..08b8c21dc1518487c7b53f27361afec0754691b7 100644 --- a/app/lib/dal/indexDAL/loki/LokiTypes.ts +++ b/app/lib/dal/indexDAL/loki/LokiTypes.ts @@ -1,5 +1,5 @@ -export interface LokiCollection<T> { +export interface RealLokiCollection<T> { data: T[] @@ -14,6 +14,17 @@ export interface LokiCollection<T> { chain(): LokiChainableFind<T> setChangesApi(disable: boolean): void + + disableChangesApi: boolean + + disableDeltaChangesApi: boolean + + changes: any[] +} + +export interface LokiCollection<T> extends RealLokiCollection<T> { + + collection: RealLokiCollection<T> } export interface LokiChainableFind<T> { diff --git a/test/dal/file-dal.ts b/test/dal/file-dal.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f243579235f86f85eaffa77f847fc1232d2cb62 --- /dev/null +++ b/test/dal/file-dal.ts @@ -0,0 +1,88 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Äž1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {assertEqual, assertFalse, assertTrue, writeBasicTestWith2Users} from "../integration/tools/test-framework" +import {TestingServer} from "../integration/tools/toolbox" +import {CommonConstants} from "../../app/lib/common-libs/constants" + +describe('File Data Access Layer', () => writeBasicTestWith2Users((test) => { + + let initialValue = CommonConstants.BLOCKS_COLLECT_THRESHOLD + + before(() => { + // Let's trim loki data every 3 blocks + CommonConstants.BLOCKS_COLLECT_THRESHOLD = 3 + }) + + after(() => { + // Revert + CommonConstants.BLOCKS_COLLECT_THRESHOLD = initialValue + }) + + test('if we disable the changes API', async (s1: TestingServer) => { + s1.dal.disableChangesAPI() + assertTrue(s1.dal.iindexDAL.lokiCollection.disableChangesApi) + assertTrue(s1.dal.iindexDAL.lokiCollection.disableDeltaChangesApi) + }) + + test('if we enable back the changes API', async (s1: TestingServer) => { + s1.dal.enableChangesAPI() + assertFalse(s1.dal.iindexDAL.lokiCollection.disableChangesApi) + assertFalse(s1.dal.iindexDAL.lokiCollection.disableDeltaChangesApi) + }) + + test('we should have no changes after commit of b#0', async (s1, cat, tac) => { + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 0) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + await cat.createIdentity() + await tac.createIdentity() + await cat.cert(tac) + await tac.cert(cat) + await cat.join() + await tac.join() + await s1.commit() + // No changes after a commit, but new data + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 2) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // Without changes files (since block#0 triggers the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 0) + }) + + test('we should have changes files after commit of b#1', async (s1, cat, tac) => { + await tac.revoke() + await s1.commit() + // Some changes, as block#1 does not provoke a lokijs data commit + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 3) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // With changes files (since block#1 does not trigger the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 1) + }) + + test('we should have one more changes files after commit of b#2', async (s1) => { + await s1.commit() + // Some changes, as block#1 does not provoke a lokijs data commit + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 3) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // With changes files (since block#1 does not trigger the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 2) + }) + + test('we should have no more changes files after commit of b#3', async (s1) => { + await s1.commit() + // Some changes, as block#1 does not provoke a lokijs data commit + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 3) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // With changes files (since block#1 does not trigger the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 0) + }) +})) diff --git a/test/integration/tools/test-framework.ts b/test/integration/tools/test-framework.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f691805f034bb3e849c865330862ed081e9b665 --- /dev/null +++ b/test/integration/tools/test-framework.ts @@ -0,0 +1,39 @@ +import {catUser, NewTestingServer, tacUser, TestingServer} from "./toolbox" +import {TestUser} from "./TestUser" +import * as assert from 'assert' + +export function writeBasicTestWith2Users(writeTests: (test: (testTitle: string, fn: (server: TestingServer, cat: TestUser, tac: TestUser) => Promise<void>) => void) => void) { + + let s1:TestingServer, cat:TestUser, tac:TestUser + + before(async () => { + s1 = NewTestingServer({ + medianTimeBlocks: 1, + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }) + cat = catUser(s1) + tac = tacUser(s1) + await s1.prepareForNetwork() + }) + + writeTests((title, cb: (server: TestingServer, cat: TestUser, tac: TestUser) => Promise<void>) => { + it(title, async () => { + await cb(s1, cat, tac) + }) + }) +} + +export function assertEqual(value: number, expected: number) { + assert.equal(value, expected) +} + +export function assertTrue(expected: boolean) { + assert.equal(true, expected) +} + +export function assertFalse(expected: boolean) { + assert.equal(false, expected) +} \ No newline at end of file diff --git a/test/integration/tools/toolbox.ts b/test/integration/tools/toolbox.ts index e833f89e16a9e18529b15c5b08b951e021215ec7..dca1313dc5179d6c091ac253f852b23723a7216e 100644 --- a/test/integration/tools/toolbox.ts +++ b/test/integration/tools/toolbox.ts @@ -733,4 +733,22 @@ export function simpleTestingConf(now = 1500000000, pair:{ pub:string, sec:strin sigQty: 1, medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime } +} + +export function catUser(server: TestingServer) { + return new TestUser('cat', { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, + { + server + }) +} + +export function tacUser(server: TestingServer) { + return new TestUser('tac', { + pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', + sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, + { + server + }) } \ No newline at end of file