about summary refs log tree commit diff
path: root/src/state/persisted/__tests__
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/persisted/__tests__')
-rw-r--r--src/state/persisted/__tests__/fixtures.ts67
-rw-r--r--src/state/persisted/__tests__/index.test.ts49
-rw-r--r--src/state/persisted/__tests__/migrate.test.ts93
-rw-r--r--src/state/persisted/__tests__/schema.test.ts (renamed from src/state/persisted/__tests__/legacy.test.ts)8
4 files changed, 217 insertions, 0 deletions
diff --git a/src/state/persisted/__tests__/fixtures.ts b/src/state/persisted/__tests__/fixtures.ts
new file mode 100644
index 000000000..ac8f7c8d1
--- /dev/null
+++ b/src/state/persisted/__tests__/fixtures.ts
@@ -0,0 +1,67 @@
+import type {LegacySchema} from '#/state/persisted/legacy'
+
+export const ALICE_DID = 'did:plc:ALICE_DID'
+export const BOB_DID = 'did:plc:BOB_DID'
+
+export const LEGACY_DATA_DUMP: LegacySchema = {
+  session: {
+    data: {
+      service: 'https://bsky.social/',
+      did: ALICE_DID,
+    },
+    accounts: [
+      {
+        service: 'https://bsky.social',
+        did: ALICE_DID,
+        refreshJwt: 'refreshJwt',
+        accessJwt: 'accessJwt',
+        handle: 'alice.test',
+        email: 'alice@bsky.test',
+        displayName: 'Alice',
+        aviUrl: 'avi',
+        emailConfirmed: true,
+      },
+      {
+        service: 'https://bsky.social',
+        did: BOB_DID,
+        refreshJwt: 'refreshJwt',
+        accessJwt: 'accessJwt',
+        handle: 'bob.test',
+        email: 'bob@bsky.test',
+        displayName: 'Bob',
+        aviUrl: 'avi',
+        emailConfirmed: true,
+      },
+    ],
+  },
+  me: {
+    did: ALICE_DID,
+    handle: 'alice.test',
+    displayName: 'Alice',
+    description: '',
+    avatar: 'avi',
+  },
+  onboarding: {step: 'Home'},
+  shell: {colorMode: 'system'},
+  preferences: {
+    primaryLanguage: 'en',
+    contentLanguages: ['en'],
+    postLanguage: 'en',
+    postLanguageHistory: ['en', 'en', 'ja', 'pt', 'de', 'en'],
+    contentLabels: {
+      nsfw: 'warn',
+      nudity: 'warn',
+      suggestive: 'warn',
+      gore: 'warn',
+      hate: 'hide',
+      spam: 'hide',
+      impersonation: 'warn',
+    },
+    savedFeeds: ['feed_a', 'feed_b', 'feed_c'],
+    pinnedFeeds: ['feed_a', 'feed_b'],
+    requireAltTextEnabled: false,
+  },
+  invitedUsers: {seenDids: [], copiedInvites: []},
+  mutedThreads: {uris: []},
+  reminders: {},
+}
diff --git a/src/state/persisted/__tests__/index.test.ts b/src/state/persisted/__tests__/index.test.ts
new file mode 100644
index 000000000..90c5e0e4e
--- /dev/null
+++ b/src/state/persisted/__tests__/index.test.ts
@@ -0,0 +1,49 @@
+import {jest, expect, test, afterEach} from '@jest/globals'
+import AsyncStorage from '@react-native-async-storage/async-storage'
+
+import {defaults} from '#/state/persisted/schema'
+import {migrate} from '#/state/persisted/legacy'
+import * as store from '#/state/persisted/store'
+import * as persisted from '#/state/persisted'
+
+const write = jest.mocked(store.write)
+const read = jest.mocked(store.read)
+
+jest.mock('#/logger')
+jest.mock('#/state/persisted/legacy', () => ({
+  migrate: jest.fn(),
+}))
+jest.mock('#/state/persisted/store', () => ({
+  write: jest.fn(),
+  read: jest.fn(),
+}))
+
+afterEach(() => {
+  jest.useFakeTimers()
+  jest.clearAllMocks()
+  AsyncStorage.clear()
+})
+
+test('init: fresh install, no migration', async () => {
+  await persisted.init()
+
+  expect(migrate).toHaveBeenCalledTimes(1)
+  expect(read).toHaveBeenCalledTimes(1)
+  expect(write).toHaveBeenCalledWith(defaults)
+
+  // default value
+  expect(persisted.get('colorMode')).toBe('system')
+})
+
+test('init: fresh install, migration ran', async () => {
+  read.mockResolvedValueOnce(defaults)
+
+  await persisted.init()
+
+  expect(migrate).toHaveBeenCalledTimes(1)
+  expect(read).toHaveBeenCalledTimes(1)
+  expect(write).not.toHaveBeenCalled()
+
+  // default value
+  expect(persisted.get('colorMode')).toBe('system')
+})
diff --git a/src/state/persisted/__tests__/migrate.test.ts b/src/state/persisted/__tests__/migrate.test.ts
new file mode 100644
index 000000000..d42580efd
--- /dev/null
+++ b/src/state/persisted/__tests__/migrate.test.ts
@@ -0,0 +1,93 @@
+import {jest, expect, test, afterEach} from '@jest/globals'
+import AsyncStorage from '@react-native-async-storage/async-storage'
+
+import {defaults, schema} from '#/state/persisted/schema'
+import {transform, migrate} from '#/state/persisted/legacy'
+import * as store from '#/state/persisted/store'
+import {logger} from '#/logger'
+import * as fixtures from '#/state/persisted/__tests__/fixtures'
+
+const write = jest.mocked(store.write)
+const read = jest.mocked(store.read)
+
+jest.mock('#/logger')
+jest.mock('#/state/persisted/store', () => ({
+  write: jest.fn(),
+  read: jest.fn(),
+}))
+
+afterEach(() => {
+  jest.clearAllMocks()
+  AsyncStorage.clear()
+})
+
+test('migrate: fresh install', async () => {
+  await migrate()
+
+  expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
+  expect(read).toHaveBeenCalledTimes(1)
+  expect(logger.log).toHaveBeenCalledWith(
+    'persisted state: no migration needed',
+  )
+})
+
+test('migrate: fresh install, existing new storage', async () => {
+  read.mockResolvedValueOnce(defaults)
+
+  await migrate()
+
+  expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
+  expect(read).toHaveBeenCalledTimes(1)
+  expect(logger.log).toHaveBeenCalledWith(
+    'persisted state: no migration needed',
+  )
+})
+
+test('migrate: fresh install, AsyncStorage error', async () => {
+  const prevGetItem = AsyncStorage.getItem
+
+  const error = new Error('test error')
+
+  AsyncStorage.getItem = jest.fn(() => {
+    throw error
+  })
+
+  await migrate()
+
+  expect(AsyncStorage.getItem).toHaveBeenCalledWith('root')
+  expect(logger.error).toHaveBeenCalledWith(error, {
+    message: 'persisted state: error migrating legacy storage',
+  })
+
+  AsyncStorage.getItem = prevGetItem
+})
+
+test('migrate: has legacy data', async () => {
+  await AsyncStorage.setItem('root', JSON.stringify(fixtures.LEGACY_DATA_DUMP))
+
+  await migrate()
+
+  expect(write).toHaveBeenCalledWith(transform(fixtures.LEGACY_DATA_DUMP))
+  expect(logger.log).toHaveBeenCalledWith(
+    'persisted state: migrated legacy storage',
+  )
+})
+
+test('migrate: has legacy data, fails validation', async () => {
+  const legacy = fixtures.LEGACY_DATA_DUMP
+  // @ts-ignore
+  legacy.shell.colorMode = 'invalid'
+  await AsyncStorage.setItem('root', JSON.stringify(legacy))
+
+  await migrate()
+
+  const transformed = transform(legacy)
+  const validate = schema.safeParse(transformed)
+
+  expect(write).not.toHaveBeenCalled()
+  expect(logger.error).toHaveBeenCalledWith(
+    'persisted state: legacy data failed validation',
+    // @ts-ignore
+    {error: validate.error},
+  )
+})
diff --git a/src/state/persisted/__tests__/legacy.test.ts b/src/state/persisted/__tests__/schema.test.ts
index 7f4b138a1..c78a2c27c 100644
--- a/src/state/persisted/__tests__/legacy.test.ts
+++ b/src/state/persisted/__tests__/schema.test.ts
@@ -2,6 +2,7 @@ import {expect, test} from '@jest/globals'
 
 import {transform} from '#/state/persisted/legacy'
 import {defaults, schema} from '#/state/persisted/schema'
+import * as fixtures from '#/state/persisted/__tests__/fixtures'
 
 test('defaults', () => {
   expect(() => schema.parse(defaults)).not.toThrow()
@@ -11,3 +12,10 @@ test('transform', () => {
   const data = transform({})
   expect(() => schema.parse(data)).not.toThrow()
 })
+
+test('transform: legacy fixture', () => {
+  const data = transform(fixtures.LEGACY_DATA_DUMP)
+  expect(() => schema.parse(data)).not.toThrow()
+  expect(data.session.currentAccount?.did).toEqual(fixtures.ALICE_DID)
+  expect(data.session.accounts.length).toEqual(2)
+})