test(12.1-03): add RED tests for server freshness mapping
- cloudConnections: setBrowseState warning/refreshing/fresh states - cloudConnections: cached items remain during warning - cloudConnections: selectConnection clears browse state - cloudConnections: does_not_use_browser_clock_as_refresh_evidence - CloudFolderView: maps_server_warning_and_last_success - CloudFolderView: http_200_does_not_imply_fresh - CloudFolderView: does_not_use_browser_clock_as_refresh_evidence - CloudFolderView: cached_items_remain_visible_during_warning 3 new failures against unconditional fresh/new Date() behavior
This commit is contained in:
@@ -156,3 +156,83 @@ describe('useCloudConnectionsStore', () => {
|
||||
expect(stored.startsWith('{')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// ── Freshness state mapping ──────────────────────────────────────────────────
|
||||
|
||||
describe('setBrowseState freshness mapping', () => {
|
||||
it('setBrowseState with freshness=warning sets folderFreshness to "warning"', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.setBrowseState({ freshness: 'warning' })
|
||||
expect(store.folderFreshness).toBe('warning')
|
||||
})
|
||||
|
||||
it('setBrowseState with freshness=refreshing sets folderFreshness to "refreshing"', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.setBrowseState({ freshness: 'refreshing' })
|
||||
expect(store.folderFreshness).toBe('refreshing')
|
||||
})
|
||||
|
||||
it('setBrowseState with freshness=fresh sets folderFreshness to "fresh"', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.setBrowseState({ freshness: 'fresh' })
|
||||
expect(store.folderFreshness).toBe('fresh')
|
||||
})
|
||||
|
||||
it('maps_server_warning_and_last_success: server last_refreshed_at is preserved exactly', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
const serverTimestamp = '2026-06-15T10:30:00Z'
|
||||
store.setBrowseState({
|
||||
freshness: 'warning',
|
||||
refreshedAt: serverTimestamp,
|
||||
})
|
||||
expect(store.folderFreshness).toBe('warning')
|
||||
expect(store.lastRefreshedAt).toBe(serverTimestamp)
|
||||
})
|
||||
|
||||
it('selectConnection clears cloud browse state without persisting secrets', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
store.connections = [{ id: 'conn-1', provider: 'google_drive' }]
|
||||
// Set some browse state
|
||||
store.setBrowseState({
|
||||
items: [{ id: 'item-1', name: 'doc.pdf' }],
|
||||
caps: { share: true },
|
||||
freshness: 'fresh',
|
||||
refreshedAt: '2026-06-15T10:30:00Z',
|
||||
byteAvail: 'cached',
|
||||
err: null,
|
||||
})
|
||||
// Select a different connection — should clear state
|
||||
store.selectConnection('conn-1')
|
||||
expect(store.browseItems).toHaveLength(0)
|
||||
expect(store.folderFreshness).toBeNull()
|
||||
expect(store.lastRefreshedAt).toBeNull()
|
||||
expect(store.capabilities).toBeNull()
|
||||
expect(store.byteAvailability).toBeNull()
|
||||
// No tokens or credentials should be present
|
||||
expect(store.browseError).toBeNull()
|
||||
})
|
||||
|
||||
it('does_not_use_browser_clock_as_refresh_evidence: refreshedAt from server, not Date.now', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
// Store only accepts what is passed explicitly — no implicit Date()
|
||||
const SERVER_TS = '2026-06-10T08:00:00.000Z'
|
||||
store.setBrowseState({ freshness: 'fresh', refreshedAt: SERVER_TS })
|
||||
// lastRefreshedAt must equal exactly what was passed — not a new Date()
|
||||
expect(store.lastRefreshedAt).toBe(SERVER_TS)
|
||||
})
|
||||
|
||||
it('cached_items_remain_visible_during_warning: items not cleared on warning freshness', () => {
|
||||
const store = useCloudConnectionsStore()
|
||||
const cachedItems = [
|
||||
{ id: 'item-a', provider_item_id: 'pref-a', name: 'Cached Folder', kind: 'folder' },
|
||||
]
|
||||
// First, set fresh state with items
|
||||
store.setBrowseState({ items: cachedItems, freshness: 'fresh', refreshedAt: '2026-06-01T00:00:00Z' })
|
||||
expect(store.browseItems).toHaveLength(1)
|
||||
// Now simulate a warning without clearing items (only freshness changes)
|
||||
store.setBrowseState({ freshness: 'warning' })
|
||||
// Items must still be there — warning does not delete cached items
|
||||
expect(store.browseItems).toHaveLength(1)
|
||||
expect(store.browseItems[0].id).toBe('item-a')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -306,3 +306,117 @@ describe('opaque_reference_round_trip', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// ── Freshness mapping: server state is authoritative ─────────────────────────
|
||||
|
||||
describe('maps_server_refreshing_freshness', () => {
|
||||
it('setBrowseState is called with refreshing before the API resolves', async () => {
|
||||
// Make the API resolve async so we can check the in-flight state
|
||||
let resolveApi
|
||||
api.getCloudFoldersByConnectionId.mockReturnValue(new Promise(r => { resolveApi = r }))
|
||||
const w = mount(CloudFolderView, { global: { stubs: globalStubs } })
|
||||
// Before resolution: refreshing must have been set
|
||||
expect(mockSetBrowseState).toHaveBeenCalledWith(expect.objectContaining({ freshness: 'refreshing' }))
|
||||
// Complete the request
|
||||
resolveApi({
|
||||
items: [],
|
||||
capabilities: null,
|
||||
freshness: { refresh_state: 'fresh', last_refreshed_at: '2026-06-22T00:00:00Z' },
|
||||
})
|
||||
await flushPromises()
|
||||
})
|
||||
})
|
||||
|
||||
describe('maps_server_warning_and_last_success', () => {
|
||||
it('HTTP 200 with warning freshness calls setBrowseState with warning, not fresh', async () => {
|
||||
const SERVER_TS = '2026-06-10T12:00:00.000Z'
|
||||
api.getCloudFoldersByConnectionId.mockResolvedValue({
|
||||
items: [FOLDER_A],
|
||||
capabilities: null,
|
||||
freshness: {
|
||||
refresh_state: 'warning',
|
||||
last_refreshed_at: SERVER_TS,
|
||||
error_code: 'incomplete_listing',
|
||||
error_message: 'Provider returned incomplete results.',
|
||||
},
|
||||
})
|
||||
const w = mount(CloudFolderView, { global: { stubs: globalStubs } })
|
||||
await flushPromises()
|
||||
// setBrowseState must be called with freshness='warning', NOT 'fresh'
|
||||
const calls = mockSetBrowseState.mock.calls
|
||||
const finalCall = calls.find(c => c[0].freshness === 'warning')
|
||||
expect(finalCall).toBeDefined()
|
||||
// fresh must NOT be set after a warning response
|
||||
const freshCall = calls.find(c => c[0].freshness === 'fresh')
|
||||
expect(freshCall).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('http_200_does_not_imply_fresh', () => {
|
||||
it('HTTP 200 with warning state does not result in fresh freshness being set', async () => {
|
||||
api.getCloudFoldersByConnectionId.mockResolvedValue({
|
||||
items: [],
|
||||
capabilities: null,
|
||||
freshness: {
|
||||
refresh_state: 'warning',
|
||||
last_refreshed_at: null,
|
||||
error_code: 'incomplete_listing',
|
||||
error_message: null,
|
||||
},
|
||||
})
|
||||
const w = mount(CloudFolderView, { global: { stubs: globalStubs } })
|
||||
await flushPromises()
|
||||
// None of the setBrowseState calls (excluding the initial 'refreshing') should set 'fresh'
|
||||
const freshAfterLoad = mockSetBrowseState.mock.calls
|
||||
.filter(c => c[0].freshness === 'fresh')
|
||||
expect(freshAfterLoad.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('does_not_use_browser_clock_as_refresh_evidence', () => {
|
||||
it('refreshedAt passed to setBrowseState comes from server last_refreshed_at, not new Date()', async () => {
|
||||
const SERVER_TS = '2026-06-01T08:00:00.000Z'
|
||||
api.getCloudFoldersByConnectionId.mockResolvedValue({
|
||||
items: [],
|
||||
capabilities: null,
|
||||
freshness: {
|
||||
refresh_state: 'fresh',
|
||||
last_refreshed_at: SERVER_TS,
|
||||
},
|
||||
})
|
||||
const w = mount(CloudFolderView, { global: { stubs: globalStubs } })
|
||||
await flushPromises()
|
||||
// Find the setBrowseState call that carries a refreshedAt
|
||||
const calls = mockSetBrowseState.mock.calls
|
||||
const callWithTs = calls.find(c => c[0].refreshedAt !== undefined && c[0].refreshedAt !== null)
|
||||
expect(callWithTs).toBeDefined()
|
||||
// The timestamp must be exactly what the server sent
|
||||
expect(callWithTs[0].refreshedAt).toBe(SERVER_TS)
|
||||
// Must NOT contain 'Z' as a result of new Date().toISOString() producing a different value
|
||||
// (if the test is run quickly, these might match by coincidence — but the semantic check is:
|
||||
// does the code use the server value or call new Date()?)
|
||||
// We verify this by checking the value matches the exact server string, not a generated one
|
||||
expect(callWithTs[0].refreshedAt).not.toMatch(/^2026-06-22/) // not today's date
|
||||
})
|
||||
})
|
||||
|
||||
describe('cached_items_remain_visible_during_warning', () => {
|
||||
it('on HTTP 200 warning, items from response are still passed to store', async () => {
|
||||
api.getCloudFoldersByConnectionId.mockResolvedValue({
|
||||
items: [FOLDER_A, FILE_A],
|
||||
capabilities: null,
|
||||
freshness: {
|
||||
refresh_state: 'warning',
|
||||
last_refreshed_at: '2026-06-01T00:00:00Z',
|
||||
error_code: 'incomplete_listing',
|
||||
error_message: null,
|
||||
},
|
||||
})
|
||||
const w = mount(CloudFolderView, { global: { stubs: globalStubs } })
|
||||
await flushPromises()
|
||||
// Items must be passed to the store even during a warning state
|
||||
const callWithItems = mockSetBrowseState.mock.calls.find(c => c[0].items !== undefined)
|
||||
expect(callWithItems).toBeDefined()
|
||||
expect(callWithItems[0].items.length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user