Merge pull request #4235 from scsaba/transaction-history-timestamps

Transaction history timestamps
feature/default_network_editable
kumavis 7 years ago committed by GitHub
commit f397002bf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      app/scripts/controllers/transactions/lib/tx-state-history-helper.js
  2. 2
      app/scripts/controllers/transactions/tx-state-manager.js
  3. 139
      test/unit/tx-state-history-helper-test.js
  4. 46
      test/unit/tx-state-history-helper.js
  5. 9
      test/unit/tx-state-manager-test.js

@ -25,26 +25,31 @@ function migrateFromSnapshotsToDiffs (longHistory) {
} }
/** /**
generates an array of history objects sense the previous state. Generates an array of history objects sense the previous state.
The object has the keys opp(the operation preformed), The object has the keys
path(the key and if a nested object then each key will be seperated with a `/`) op (the operation performed),
value path (the key and if a nested object then each key will be seperated with a `/`)
with the first entry having the note value
with the first entry having the note and a timestamp when the change took place
@param previousState {object} - the previous state of the object @param previousState {object} - the previous state of the object
@param newState {object} - the update object @param newState {object} - the update object
@param note {string} - a optional note for the state change @param note {string} - a optional note for the state change
@reurns {array} @returns {array}
*/ */
function generateHistoryEntry (previousState, newState, note) { function generateHistoryEntry (previousState, newState, note) {
const entry = jsonDiffer.compare(previousState, newState) const entry = jsonDiffer.compare(previousState, newState)
// Add a note to the first op, since it breaks if we append it to the entry // Add a note to the first op, since it breaks if we append it to the entry
if (note && entry[0]) entry[0].note = note if (entry[0]) {
if (note) entry[0].note = note
entry[0].timestamp = Date.now()
}
return entry return entry
} }
/** /**
Recovers previous txMeta state obj Recovers previous txMeta state obj
@return {object} @returns {object}
*/ */
function replayHistory (_shortHistory) { function replayHistory (_shortHistory) {
const shortHistory = clone(_shortHistory) const shortHistory = clone(_shortHistory)

@ -158,7 +158,7 @@ class TransactionStateManager extends EventEmitter {
/** /**
updates the txMeta in the list and adds a history entry updates the txMeta in the list and adds a history entry
@param txMeta {Object} - the txMeta to update @param txMeta {Object} - the txMeta to update
@param [note] {string} - a not about the update for history @param [note] {string} - a note about the update for history
*/ */
updateTx (txMeta, note) { updateTx (txMeta, note) {
// validate txParams // validate txParams

@ -1,26 +1,129 @@
const assert = require('assert') const assert = require('assert')
const clone = require('clone')
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
const testVault = require('../data/v17-long-history.json')
describe('deepCloneFromTxMeta', function () { describe ('Transaction state history helper', function () {
it('should clone deep', function () {
const input = { describe('#snapshotFromTxMeta', function () {
foo: { it('should clone deep', function () {
bar: { const input = {
bam: 'baz' foo: {
bar: {
bam: 'baz'
}
} }
} }
} const output = txStateHistoryHelper.snapshotFromTxMeta(input)
const output = txStateHistoryHelper.snapshotFromTxMeta(input) assert('foo' in output, 'has a foo key')
assert('foo' in output, 'has a foo key') assert('bar' in output.foo, 'has a bar key')
assert('bar' in output.foo, 'has a bar key') assert('bam' in output.foo.bar, 'has a bar key')
assert('bam' in output.foo.bar, 'has a bar key') assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
assert.equal(output.foo.bar.bam, 'baz', 'has a baz value') })
it('should remove the history key', function () {
const input = { foo: 'bar', history: 'remembered' }
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
assert(typeof output.history, 'undefined', 'should remove history')
})
}) })
it('should remove the history key', function () { describe('#migrateFromSnapshotsToDiffs', function () {
const input = { foo: 'bar', history: 'remembered' } it('migrates history to diffs and can recover original values', function () {
const output = txStateHistoryHelper.snapshotFromTxMeta(input) testVault.data.TransactionController.transactions.forEach((tx, index) => {
assert(typeof output.history, 'undefined', 'should remove history') const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
} else {
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
}
const oldEntry = tx.history[index]
const historySubset = newHistory.slice(0, index + 1)
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
})
})
})
})
describe('#replayHistory', function () {
it('replaying history does not mutate the original obj', function () {
const initialState = { test: true, message: 'hello', value: 1 }
const diff1 = [{
"op": "replace",
"path": "/message",
"value": "haay",
}]
const diff2 = [{
"op": "replace",
"path": "/value",
"value": 2,
}]
const history = [initialState, diff1, diff2]
const beforeStateSnapshot = JSON.stringify(initialState)
const latestState = txStateHistoryHelper.replayHistory(history)
const afterStateSnapshot = JSON.stringify(initialState)
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
})
})
describe('#generateHistoryEntry', function () {
function generateHistoryEntryTest(note) {
const prevState = {
someValue: 'value 1',
foo: {
bar: {
bam: 'baz'
}
}
}
const nextState = {
newPropRoot: 'new property - root',
someValue: 'value 2',
foo: {
newPropFirstLevel: 'new property - first level',
bar: {
bam: 'baz'
}
}
}
const before = new Date().getTime()
const result = txStateHistoryHelper.generateHistoryEntry(prevState, nextState, note)
const after = new Date().getTime()
assert.ok(Array.isArray(result))
assert.equal(result.length, 3)
const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' }
assert.equal(result[0].op, expectedEntry1.op)
assert.equal(result[0].path, expectedEntry1.path)
assert.equal(result[0].value, expectedEntry1.value)
assert.equal(result[0].value, expectedEntry1.value)
if (note)
assert.equal(result[0].note, note)
assert.ok(result[0].timestamp >= before && result[0].timestamp <= after)
const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' }
assert.deepEqual(result[1], expectedEntry2)
const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' }
assert.deepEqual(result[2], expectedEntry3)
}
it('should generate history entries', function () {
generateHistoryEntryTest()
})
it('should add note to first entry', function () {
generateHistoryEntryTest('custom note')
})
}) })
}) })

@ -1,46 +0,0 @@
const assert = require('assert')
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
const testVault = require('../data/v17-long-history.json')
describe('tx-state-history-helper', function () {
it('migrates history to diffs and can recover original values', function () {
testVault.data.TransactionController.transactions.forEach((tx, index) => {
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
} else {
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
}
const oldEntry = tx.history[index]
const historySubset = newHistory.slice(0, index + 1)
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
})
})
})
it('replaying history does not mutate the original obj', function () {
const initialState = { test: true, message: 'hello', value: 1 }
const diff1 = [{
"op": "replace",
"path": "/message",
"value": "haay",
}]
const diff2 = [{
"op": "replace",
"path": "/value",
"value": 2,
}]
const history = [initialState, diff1, diff2]
const beforeStateSnapshot = JSON.stringify(initialState)
const latestState = txStateHistoryHelper.replayHistory(history)
const afterStateSnapshot = JSON.stringify(initialState)
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
})
})

@ -176,14 +176,21 @@ describe('TransactionStateManager', function () {
assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state') assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state')
// modify value and updateTx // modify value and updateTx
updatedTx.txParams.gasPrice = desiredGasPrice updatedTx.txParams.gasPrice = desiredGasPrice
const before = new Date().getTime()
txStateManager.updateTx(updatedTx) txStateManager.updateTx(updatedTx)
const after = new Date().getTime()
// check updated value // check updated value
const result = txStateManager.getTx('1') const result = txStateManager.getTx('1')
assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated')
// validate history was updated // validate history was updated
assert.equal(result.history.length, 2, 'two history items (initial + diff)') assert.equal(result.history.length, 2, 'two history items (initial + diff)')
assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)')
const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice }
assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)') assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation')
assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path')
assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value')
assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after)
}) })
}) })

Loading…
Cancel
Save