From 20cf7606b5b85aa9766e829316e0b93bc149d938 Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Tue, 10 Jul 2018 18:16:59 +0200
Subject: [PATCH] [enh] tests: check that Loki changes are well persisted and
 trimmed

---
 app/lib/dal/drivers/LokiFsAdapter.ts          |  8 ++
 app/lib/dal/drivers/LokiJsDriver.ts           |  4 +
 app/lib/dal/indexDAL/loki/LokiCollection.ts   | 17 +++-
 .../indexDAL/loki/LokiCollectionManager.ts    |  4 +
 app/lib/dal/indexDAL/loki/LokiDAO.ts          |  3 +
 app/lib/dal/indexDAL/loki/LokiTypes.ts        | 13 ++-
 test/dal/file-dal.ts                          | 88 +++++++++++++++++++
 test/integration/tools/test-framework.ts      | 39 ++++++++
 test/integration/tools/toolbox.ts             | 18 ++++
 9 files changed, 192 insertions(+), 2 deletions(-)
 create mode 100644 test/dal/file-dal.ts
 create mode 100644 test/integration/tools/test-framework.ts

diff --git a/app/lib/dal/drivers/LokiFsAdapter.ts b/app/lib/dal/drivers/LokiFsAdapter.ts
index 4d278753e..e7f5342cc 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 b895cfefc..7a3dfc549 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 04ee58878..e160608b6 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 fe0484af9..ce6d2ad06 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 9c0a9ffba..b060804d7 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 ff34a77f4..08b8c21dc 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 000000000..1f2435792
--- /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 000000000..0f691805f
--- /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 e833f89e1..dca1313dc 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
-- 
GitLab