419 lines
No EOL
9 KiB
TypeScript
419 lines
No EOL
9 KiB
TypeScript
/**
|
|
* Tests for SearchAdapter
|
|
*
|
|
* These tests verify search functionality including filtering,
|
|
* pagination, and proper request/response handling.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { SearchAdapter } from '../search.adapter'
|
|
import type { SearchParams, SearchResponse } from '../search.adapter'
|
|
|
|
describe('SearchAdapter', () => {
|
|
let adapter: SearchAdapter
|
|
let originalFetch: typeof global.fetch
|
|
|
|
beforeEach(() => {
|
|
originalFetch = global.fetch
|
|
adapter = new SearchAdapter({
|
|
baseURL: 'https://api.example.com'
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch
|
|
adapter.cancelAll()
|
|
})
|
|
|
|
describe('searchAll', () => {
|
|
it('should search across all entity types', async () => {
|
|
const mockResponse: SearchResponse = {
|
|
results: [
|
|
{
|
|
id: '1',
|
|
granblueId: 'weapon_1',
|
|
name: { en: 'Bahamut Sword', ja: 'バハムートソード' },
|
|
element: 1,
|
|
rarity: 5,
|
|
searchableType: 'Weapon',
|
|
imageUrl: 'https://example.com/weapon1.jpg'
|
|
},
|
|
{
|
|
id: '2',
|
|
granblueId: 'character_1',
|
|
name: { en: 'Bahamut', ja: 'バハムート' },
|
|
element: 6,
|
|
rarity: 5,
|
|
searchableType: 'Character',
|
|
imageUrl: 'https://example.com/character1.jpg'
|
|
}
|
|
],
|
|
total: 2,
|
|
page: 1,
|
|
totalPages: 1,
|
|
meta: {
|
|
count: 2,
|
|
page: 1,
|
|
perPage: 20,
|
|
totalPages: 1
|
|
}
|
|
}
|
|
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
results: [
|
|
{
|
|
id: '1',
|
|
granblue_id: 'weapon_1',
|
|
name: { en: 'Bahamut Sword', ja: 'バハムートソード' },
|
|
element: 1,
|
|
rarity: 5,
|
|
searchable_type: 'Weapon',
|
|
image_url: 'https://example.com/weapon1.jpg'
|
|
},
|
|
{
|
|
id: '2',
|
|
granblue_id: 'character_1',
|
|
name: { en: 'Bahamut', ja: 'バハムート' },
|
|
element: 6,
|
|
rarity: 5,
|
|
searchable_type: 'Character',
|
|
image_url: 'https://example.com/character1.jpg'
|
|
}
|
|
],
|
|
total: 2,
|
|
page: 1,
|
|
total_pages: 1,
|
|
meta: {
|
|
count: 2,
|
|
page: 1,
|
|
per_page: 20,
|
|
total_pages: 1
|
|
}
|
|
})
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'bahamut',
|
|
locale: 'en',
|
|
page: 1
|
|
}
|
|
|
|
const result = await adapter.searchAll(params)
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/search/all',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
credentials: 'omit',
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 1,
|
|
query: 'bahamut'
|
|
})
|
|
})
|
|
)
|
|
|
|
expect(result).toEqual(mockResponse)
|
|
})
|
|
|
|
it('should include filters when provided', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'sword',
|
|
filters: {
|
|
element: [1, 2],
|
|
rarity: [5],
|
|
extra: true
|
|
}
|
|
}
|
|
|
|
await adapter.searchAll(params)
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 1,
|
|
query: 'sword',
|
|
filters: {
|
|
element: [1, 2],
|
|
rarity: [5],
|
|
extra: true
|
|
}
|
|
})
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should handle exclude parameter', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'test',
|
|
exclude: ['1', '2', '3']
|
|
}
|
|
|
|
await adapter.searchAll(params)
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 1,
|
|
query: 'test',
|
|
exclude: ['1', '2', '3']
|
|
})
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should cache results when query is provided', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [{ id: '1' }] })
|
|
})
|
|
|
|
// First call
|
|
await adapter.searchAll({ query: 'test' })
|
|
expect(global.fetch).toHaveBeenCalledTimes(1)
|
|
|
|
// Second call should use cache
|
|
await adapter.searchAll({ query: 'test' })
|
|
expect(global.fetch).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should not cache results when query is empty', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
// First call
|
|
await adapter.searchAll({})
|
|
expect(global.fetch).toHaveBeenCalledTimes(1)
|
|
|
|
// Second call should not use cache
|
|
await adapter.searchAll({})
|
|
expect(global.fetch).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('searchWeapons', () => {
|
|
it('should search for weapons with appropriate filters', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'sword',
|
|
filters: {
|
|
element: [1],
|
|
proficiency1: [2],
|
|
extra: false
|
|
},
|
|
per: 50
|
|
}
|
|
|
|
await adapter.searchWeapons(params)
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/search/weapons',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
credentials: 'omit',
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 1,
|
|
per: 50,
|
|
query: 'sword',
|
|
filters: {
|
|
element: [1],
|
|
proficiency1: [2],
|
|
extra: false
|
|
}
|
|
})
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should not include character-specific filters', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'test',
|
|
filters: {
|
|
proficiency2: [1], // Character-only filter
|
|
subaura: true // Summon-only filter
|
|
}
|
|
}
|
|
|
|
await adapter.searchWeapons(params)
|
|
|
|
const calledBody = JSON.parse((global.fetch as any).mock.calls[0][1].body)
|
|
expect(calledBody.filters).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('searchCharacters', () => {
|
|
it('should search for characters with appropriate filters', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'katalina',
|
|
filters: {
|
|
element: [2],
|
|
proficiency1: [1],
|
|
proficiency2: [3]
|
|
}
|
|
}
|
|
|
|
await adapter.searchCharacters(params)
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/search/characters',
|
|
expect.objectContaining({
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 1,
|
|
query: 'katalina',
|
|
filters: {
|
|
element: [2],
|
|
proficiency1: [1],
|
|
proficiency2: [3]
|
|
}
|
|
})
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('searchSummons', () => {
|
|
it('should search for summons with appropriate filters', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [] })
|
|
})
|
|
|
|
const params: SearchParams = {
|
|
query: 'bahamut',
|
|
filters: {
|
|
element: [6],
|
|
rarity: [5],
|
|
subaura: true
|
|
}
|
|
}
|
|
|
|
await adapter.searchSummons(params)
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/search/summons',
|
|
expect.objectContaining({
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 1,
|
|
query: 'bahamut',
|
|
filters: {
|
|
element: [6],
|
|
rarity: [5],
|
|
subaura: true
|
|
}
|
|
})
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('clearSearchCache', () => {
|
|
it('should clear cached search results', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ results: [{ id: '1' }] })
|
|
})
|
|
|
|
// Cache a search
|
|
await adapter.searchAll({ query: 'test' })
|
|
expect(global.fetch).toHaveBeenCalledTimes(1)
|
|
|
|
// Clear cache
|
|
adapter.clearSearchCache()
|
|
|
|
// Should fetch again
|
|
await adapter.searchAll({ query: 'test' })
|
|
expect(global.fetch).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('error handling', () => {
|
|
it('should handle server errors', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 500,
|
|
statusText: 'Internal Server Error',
|
|
json: async () => ({ error: 'Server error' })
|
|
})
|
|
|
|
await expect(adapter.searchAll({ query: 'test' })).rejects.toMatchObject({
|
|
code: 'SERVER_ERROR',
|
|
status: 500
|
|
})
|
|
})
|
|
|
|
it('should handle network errors', async () => {
|
|
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'))
|
|
|
|
await expect(adapter.searchAll({ query: 'test' })).rejects.toMatchObject({
|
|
code: 'UNKNOWN_ERROR'
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('pagination', () => {
|
|
it('should handle pagination parameters', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
results: []
|
|
})
|
|
})
|
|
|
|
const result = await adapter.searchAll({
|
|
query: 'test',
|
|
page: 2,
|
|
per: 20
|
|
})
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
body: JSON.stringify({
|
|
locale: 'en',
|
|
page: 2,
|
|
per: 20,
|
|
query: 'test'
|
|
})
|
|
})
|
|
)
|
|
|
|
// Unified search only returns results, no pagination metadata
|
|
expect(result.results).toEqual([])
|
|
})
|
|
})
|
|
}) |