pleroma-fe/test/unit/specs/modules/serverSideStorage.spec.js

526 lines
16 KiB
JavaScript
Raw Normal View History

2022-08-04 22:09:42 +03:00
import { cloneDeep } from 'lodash'
2026-01-06 16:23:17 +02:00
import { createPinia, setActivePinia } from 'pinia'
2022-08-04 22:09:42 +03:00
import {
_getAllFlags,
2026-01-06 16:23:17 +02:00
_getRecentData,
2022-08-04 22:09:42 +03:00
_mergeFlags,
2022-08-10 02:19:07 +03:00
_mergePrefs,
2026-01-06 16:23:17 +02:00
_moveItemInArray,
2022-08-04 22:09:42 +03:00
_resetFlags,
2026-01-06 16:23:17 +02:00
COMMAND_TRIM_FLAGS,
COMMAND_TRIM_FLAGS_AND_RESET,
2022-08-04 22:09:42 +03:00
defaultState,
2025-03-23 20:03:25 +02:00
newUserFlags,
useServerSideStorageStore,
2026-01-06 16:23:17 +02:00
VERSION,
2025-03-23 20:03:25 +02:00
} from 'src/stores/serverSideStorage.js'
2022-08-04 22:09:42 +03:00
describe('The serverSideStorage module', () => {
2025-03-23 20:03:25 +02:00
beforeEach(() => {
setActivePinia(createPinia())
})
2022-08-04 22:09:42 +03:00
describe('mutations', () => {
describe('setServerSideStorage', () => {
const user = {
created_at: new Date('1999-02-09'),
2026-01-06 16:22:52 +02:00
storage: {},
2022-08-04 22:09:42 +03:00
}
it('should initialize storage if none present', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setServerSideStorage(store, user)
expect(store.cache._version).to.eql(VERSION)
expect(store.cache._timestamp).to.be.a('number')
expect(store.cache.flagStorage).to.eql(defaultState.flagStorage)
expect(store.cache.prefsStorage).to.eql(defaultState.prefsStorage)
2022-08-04 22:09:42 +03:00
})
it('should initialize storage with proper flags for new users if none present', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setServerSideStorage({ ...user, created_at: new Date() })
expect(store.cache._version).to.eql(VERSION)
expect(store.cache._timestamp).to.be.a('number')
expect(store.cache.flagStorage).to.eql(newUserFlags)
expect(store.cache.prefsStorage).to.eql(defaultState.prefsStorage)
2022-08-04 22:09:42 +03:00
})
it('should merge flags even if remote timestamp is older', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.cache = {
_timestamp: Date.now(),
_version: VERSION,
2026-01-06 16:22:52 +02:00
...cloneDeep(defaultState),
2022-08-04 22:09:42 +03:00
}
2025-03-23 20:03:25 +02:00
2026-01-06 16:22:52 +02:00
store.setServerSideStorage({
...user,
storage: {
_timestamp: 123,
_version: VERSION,
flagStorage: {
...defaultState.flagStorage,
updateCounter: 1,
},
prefsStorage: {
...defaultState.prefsStorage,
},
},
})
2025-03-23 20:03:25 +02:00
expect(store.cache.flagStorage).to.eql({
2022-08-04 22:09:42 +03:00
...defaultState.flagStorage,
2026-01-06 16:22:52 +02:00
updateCounter: 1,
2022-08-04 22:09:42 +03:00
})
})
2025-02-03 16:29:18 +02:00
it('should reset local timestamp to remote if contents are the same', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.cache = null
2026-01-06 16:22:52 +02:00
store.setServerSideStorage({
...user,
storage: {
_timestamp: 123,
_version: VERSION,
flagStorage: {
...defaultState.flagStorage,
updateCounter: 999,
},
},
})
2025-03-23 20:03:25 +02:00
expect(store.cache._timestamp).to.eql(123)
expect(store.flagStorage.updateCounter).to.eql(999)
expect(store.cache.flagStorage.updateCounter).to.eql(999)
2022-08-04 22:09:42 +03:00
})
it('should remote version if local missing', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setServerSideStorage(store, user)
expect(store.cache._version).to.eql(VERSION)
expect(store.cache._timestamp).to.be.a('number')
expect(store.cache.flagStorage).to.eql(defaultState.flagStorage)
2022-08-04 22:09:42 +03:00
})
})
describe('setPreference', () => {
it('should set preference and update journal log accordingly', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.testing', value: 1 })
expect(store.prefsStorage.simple.testing).to.eql(1)
expect(store.prefsStorage._journal.length).to.eql(1)
expect(store.prefsStorage._journal[0]).to.eql({
path: 'simple.testing',
2022-08-12 00:50:08 +03:00
operation: 'set',
args: [1],
// should have A timestamp, we don't really care what it is
2026-01-06 16:22:52 +02:00
timestamp: store.prefsStorage._journal[0].timestamp,
})
})
it('should keep journal to a minimum', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.testing', value: 1 })
store.setPreference({ path: 'simple.testing', value: 2 })
store.addCollectionPreference({ path: 'collections.testing', value: 2 })
2026-01-06 16:22:52 +02:00
store.removeCollectionPreference({
path: 'collections.testing',
value: 2,
})
2025-03-23 20:03:25 +02:00
store.updateCache({ username: 'test' })
expect(store.prefsStorage.simple.testing).to.eql(2)
expect(store.prefsStorage.collections.testing).to.eql([])
expect(store.prefsStorage._journal.length).to.eql(2)
expect(store.prefsStorage._journal[0]).to.eql({
path: 'simple.testing',
2022-08-12 00:50:08 +03:00
operation: 'set',
args: [2],
// should have A timestamp, we don't really care what it is
2026-01-06 16:22:52 +02:00
timestamp: store.prefsStorage._journal[0].timestamp,
})
2025-03-23 20:03:25 +02:00
expect(store.prefsStorage._journal[1]).to.eql({
2022-08-16 20:14:18 +03:00
path: 'collections.testing',
operation: 'removeFromCollection',
args: [2],
// should have A timestamp, we don't really care what it is
2026-01-06 16:22:52 +02:00
timestamp: store.prefsStorage._journal[1].timestamp,
})
})
it('should remove duplicate entries from journal', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.testing', value: 1 })
store.setPreference({ path: 'simple.testing', value: 1 })
store.addCollectionPreference({ path: 'collections.testing', value: 2 })
store.addCollectionPreference({ path: 'collections.testing', value: 2 })
store.updateCache({ username: 'test' })
expect(store.prefsStorage.simple.testing).to.eql(1)
expect(store.prefsStorage.collections.testing).to.eql([2])
expect(store.prefsStorage._journal.length).to.eql(2)
})
it('should remove depth = 3 set/unset entries from journal', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.object.foo', value: 1 })
store.unsetPreference({ path: 'simple.object.foo' })
store.updateCache(store, { username: 'test' })
expect(store.prefsStorage.simple.object).to.not.have.property('foo')
expect(store.prefsStorage._journal.length).to.eql(1)
})
it('should not allow unsetting depth <= 2', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.object.foo', value: 1 })
expect(() => store.unsetPreference({ path: 'simple' })).to.throw()
2026-01-06 16:22:52 +02:00
expect(() =>
store.unsetPreference({ path: 'simple.object' }),
).to.throw()
2025-03-23 17:23:47 +02:00
})
it('should not allow (un)setting depth > 3', () => {
2025-03-23 20:03:25 +02:00
const store = useServerSideStorageStore()
store.setPreference({ path: 'simple.object', value: {} })
2026-01-06 16:22:52 +02:00
expect(() =>
store.setPreference({ path: 'simple.object.lv3', value: 1 }),
).to.not.throw()
expect(() =>
store.setPreference({ path: 'simple.object.lv3.lv4', value: 1 }),
).to.throw()
expect(() =>
store.unsetPreference({ path: 'simple.object.lv3', value: 1 }),
).to.not.throw()
expect(() =>
store.unsetPreference({ path: 'simple.object.lv3.lv4', value: 1 }),
).to.throw()
})
})
2022-08-04 22:09:42 +03:00
})
describe('helper functions', () => {
describe('_moveItemInArray', () => {
it('should move item according to movement value', () => {
expect(_moveItemInArray([1, 2, 3, 4], 4, -1)).to.eql([1, 2, 4, 3])
expect(_moveItemInArray([1, 2, 3, 4], 1, 2)).to.eql([2, 3, 1, 4])
})
it('should clamp movement to within array', () => {
expect(_moveItemInArray([1, 2, 3, 4], 4, -10)).to.eql([4, 1, 2, 3])
expect(_moveItemInArray([1, 2, 3, 4], 3, 99)).to.eql([1, 2, 4, 3])
})
})
2022-08-04 22:09:42 +03:00
describe('_getRecentData', () => {
it('should handle nulls correctly', () => {
2026-01-06 16:22:52 +02:00
expect(_getRecentData(null, null, true)).to.eql({
recent: null,
stale: null,
needUpload: true,
})
2022-08-04 22:09:42 +03:00
})
2026-01-06 16:22:52 +02:00
it("doesn't choke on invalid data", () => {
expect(_getRecentData({ a: 1 }, { b: 2 }, true)).to.eql({
recent: null,
stale: null,
needUpload: true,
})
2022-08-04 22:09:42 +03:00
})
it('should prefer the valid non-null correctly, needUpload works properly', () => {
const nonNull = { _version: VERSION, _timestamp: 1 }
2026-01-06 16:22:52 +02:00
expect(_getRecentData(nonNull, null, true)).to.eql({
recent: nonNull,
stale: null,
needUpload: true,
})
expect(_getRecentData(null, nonNull, true)).to.eql({
recent: nonNull,
stale: null,
needUpload: false,
})
2022-08-04 22:09:42 +03:00
})
it('should prefer the one with higher timestamp', () => {
const a = { _version: VERSION, _timestamp: 1 }
const b = { _version: VERSION, _timestamp: 2 }
2026-01-06 16:22:52 +02:00
expect(_getRecentData(a, b, true)).to.eql({
recent: b,
stale: a,
needUpload: false,
})
expect(_getRecentData(b, a, true)).to.eql({
recent: b,
stale: a,
needUpload: false,
})
2022-08-04 22:09:42 +03:00
})
it('case where both are same', () => {
const a = { _version: VERSION, _timestamp: 3 }
const b = { _version: VERSION, _timestamp: 3 }
2026-01-06 16:22:52 +02:00
expect(_getRecentData(a, b, true)).to.eql({
recent: b,
stale: a,
needUpload: false,
})
expect(_getRecentData(b, a, true)).to.eql({
recent: b,
stale: a,
needUpload: false,
})
2022-08-04 22:09:42 +03:00
})
})
describe('_getAllFlags', () => {
it('should handle nulls properly', () => {
expect(_getAllFlags(null, null)).to.eql([])
})
it('should output list of keys if passed single object', () => {
2026-01-06 16:22:52 +02:00
expect(
_getAllFlags({ flagStorage: { a: 1, b: 1, c: 1 } }, null),
).to.eql(['a', 'b', 'c'])
2022-08-04 22:09:42 +03:00
})
it('should union keys of both objects', () => {
2026-01-06 16:22:52 +02:00
expect(
_getAllFlags(
{ flagStorage: { a: 1, b: 1, c: 1 } },
{ flagStorage: { c: 1, d: 1 } },
),
).to.eql(['a', 'b', 'c', 'd'])
2022-08-04 22:09:42 +03:00
})
})
describe('_mergeFlags', () => {
it('should handle merge two flag sets correctly picking higher numbers', () => {
expect(
_mergeFlags(
{ flagStorage: { a: 0, b: 3 } },
{ flagStorage: { b: 1, c: 4, d: 9 } },
2026-01-06 16:22:52 +02:00
['a', 'b', 'c', 'd'],
),
2022-08-04 22:09:42 +03:00
).to.eql({ a: 0, b: 3, c: 4, d: 9 })
})
})
2022-08-10 02:19:07 +03:00
describe('_mergePrefs', () => {
it('should prefer recent and apply journal to it', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { a: 1, b: 0, c: true },
_journal: [
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
2026-01-06 16:22:52 +02:00
{
path: 'simple.c',
operation: 'set',
args: [true],
timestamp: 4,
},
],
2022-08-10 02:19:07 +03:00
},
// STALE
{
simple: { a: 1, b: 1, c: false },
_journal: [
{ path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
2026-01-06 16:22:52 +02:00
{ path: 'simple.b', operation: 'set', args: [1], timestamp: 3 },
],
},
),
2022-08-10 02:19:07 +03:00
).to.eql({
simple: { a: 1, b: 1, c: true },
_journal: [
{ path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
{ path: 'simple.b', operation: 'set', args: [1], timestamp: 3 },
2026-01-06 16:22:52 +02:00
{ path: 'simple.c', operation: 'set', args: [true], timestamp: 4 },
],
2022-08-10 02:19:07 +03:00
})
})
it('should allow setting falsy values', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { a: 1, b: 0, c: false },
_journal: [
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
2026-01-06 16:22:52 +02:00
{
path: 'simple.c',
operation: 'set',
args: [false],
timestamp: 4,
},
],
2022-08-10 02:19:07 +03:00
},
// STALE
{
simple: { a: 0, b: 0, c: true },
_journal: [
{ path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
2026-01-06 16:22:52 +02:00
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 3 },
],
},
),
2022-08-10 02:19:07 +03:00
).to.eql({
simple: { a: 0, b: 0, c: false },
_journal: [
{ path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
{ path: 'simple.b', operation: 'set', args: [0], timestamp: 3 },
2026-01-06 16:22:52 +02:00
{ path: 'simple.c', operation: 'set', args: [false], timestamp: 4 },
],
2022-08-10 02:19:07 +03:00
})
})
it('should work with strings', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { a: 'foo' },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.a',
operation: 'set',
args: ['foo'],
timestamp: 2,
},
],
2022-08-10 02:19:07 +03:00
},
// STALE
{
simple: { a: 'bar' },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.a',
operation: 'set',
args: ['bar'],
timestamp: 4,
},
],
},
),
2022-08-10 02:19:07 +03:00
).to.eql({
simple: { a: 'bar' },
_journal: [
2026-01-06 16:22:52 +02:00
{ path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 },
],
2022-08-10 02:19:07 +03:00
})
})
it('should work with objects', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { lv2: { lv3: 'foo' } },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.lv2.lv3',
operation: 'set',
args: ['foo'],
timestamp: 2,
},
],
},
// STALE
{
simple: { lv2: { lv3: 'bar' } },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.lv2.lv3',
operation: 'set',
args: ['bar'],
timestamp: 4,
},
],
},
),
).to.eql({
simple: { lv2: { lv3: 'bar' } },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.lv2.lv3',
operation: 'set',
args: ['bar'],
timestamp: 4,
},
],
})
})
it('should work with unset', () => {
expect(
_mergePrefs(
// RECENT
{
simple: { lv2: { lv3: 'foo' } },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.lv2.lv3',
operation: 'set',
args: ['foo'],
timestamp: 2,
},
],
},
// STALE
{
simple: { lv2: {} },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.lv2.lv3',
operation: 'unset',
args: [],
timestamp: 4,
},
],
},
),
).to.eql({
simple: { lv2: {} },
_journal: [
2026-01-06 16:22:52 +02:00
{
path: 'simple.lv2.lv3',
operation: 'unset',
args: [],
timestamp: 4,
},
],
})
})
2022-08-10 02:19:07 +03:00
})
2022-08-04 22:09:42 +03:00
describe('_resetFlags', () => {
it('should reset all known flags to 0 when reset flag is set to > 0 and < 9000', () => {
const totalFlags = { a: 0, b: 3, reset: 1 }
expect(_resetFlags(totalFlags)).to.eql({ a: 0, b: 0, reset: 0 })
})
it('should trim all flags to known when reset is set to 1000', () => {
const totalFlags = { a: 0, b: 3, c: 33, reset: COMMAND_TRIM_FLAGS }
2026-01-06 16:22:52 +02:00
expect(_resetFlags(totalFlags, { a: 0, b: 0, reset: 0 })).to.eql({
a: 0,
b: 3,
reset: 0,
})
2022-08-04 22:09:42 +03:00
})
it('should trim all flags to known and reset when reset is set to 1001', () => {
2026-01-06 16:22:52 +02:00
const totalFlags = {
a: 0,
b: 3,
c: 33,
reset: COMMAND_TRIM_FLAGS_AND_RESET,
}
2022-08-04 22:09:42 +03:00
2026-01-06 16:22:52 +02:00
expect(_resetFlags(totalFlags, { a: 0, b: 0, reset: 0 })).to.eql({
a: 0,
b: 0,
reset: 0,
})
2022-08-04 22:09:42 +03:00
})
})
})
})