171 lines
6.1 KiB
JavaScript
171 lines
6.1 KiB
JavaScript
/**
|
|
* Property-based tests for user API
|
|
* **Feature: miniapp-frontend, Property 19: Search Parameters**
|
|
* **Validates: Requirements 11.2**
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import * as fc from 'fast-check'
|
|
|
|
// Mock uni object
|
|
vi.mock('../../__tests__/mocks/uni.js', () => ({}))
|
|
|
|
// Create a mock for the request module to capture API calls
|
|
let capturedRequest = null
|
|
|
|
vi.mock('../../api/request', () => ({
|
|
post: vi.fn((url, data) => {
|
|
capturedRequest = { url, data }
|
|
return Promise.resolve({ success: true, data: { items: [], total: 0 } })
|
|
}),
|
|
get: vi.fn((url, data) => {
|
|
capturedRequest = { url, data }
|
|
return Promise.resolve({ success: true, data: [] })
|
|
})
|
|
}))
|
|
|
|
// Import after mocking
|
|
import { search } from '../../api/user.js'
|
|
import { post } from '../../api/request'
|
|
|
|
describe('User API Property Tests', () => {
|
|
beforeEach(() => {
|
|
capturedRequest = null
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
/**
|
|
* Property 19: Search Parameters
|
|
* *For any* search submission, the API call SHALL include all filter parameters
|
|
* matching the user's selections.
|
|
* **Validates: Requirements 11.2**
|
|
*/
|
|
describe('Property 19: Search Parameters', () => {
|
|
// Arbitrary for search parameters
|
|
const searchParamsArb = fc.record({
|
|
ageMin: fc.option(fc.integer({ min: 18, max: 50 }), { nil: undefined }),
|
|
ageMax: fc.option(fc.integer({ min: 18, max: 50 }), { nil: undefined }),
|
|
heightMin: fc.option(fc.integer({ min: 140, max: 200 }), { nil: undefined }),
|
|
heightMax: fc.option(fc.integer({ min: 140, max: 200 }), { nil: undefined }),
|
|
education: fc.option(fc.array(fc.integer({ min: 1, max: 7 }), { minLength: 0, maxLength: 5 }), { nil: undefined }),
|
|
province: fc.option(fc.stringOf(fc.constantFrom('北京', '上海', '广东', '浙江', '江苏'), { minLength: 0, maxLength: 1 }), { nil: undefined }),
|
|
city: fc.option(fc.stringOf(fc.constantFrom('北京', '上海', '广州', '深圳', '杭州'), { minLength: 0, maxLength: 1 }), { nil: undefined }),
|
|
monthlyIncomeMin: fc.option(fc.integer({ min: 0, max: 100000 }), { nil: undefined }),
|
|
monthlyIncomeMax: fc.option(fc.integer({ min: 0, max: 100000 }), { nil: undefined }),
|
|
houseStatus: fc.option(fc.array(fc.integer({ min: 1, max: 4 }), { minLength: 0, maxLength: 4 }), { nil: undefined }),
|
|
carStatus: fc.option(fc.array(fc.integer({ min: 1, max: 3 }), { minLength: 0, maxLength: 3 }), { nil: undefined }),
|
|
marriageStatus: fc.option(fc.array(fc.integer({ min: 1, max: 4 }), { minLength: 0, maxLength: 4 }), { nil: undefined }),
|
|
pageIndex: fc.option(fc.integer({ min: 1, max: 100 }), { nil: undefined }),
|
|
pageSize: fc.option(fc.integer({ min: 10, max: 50 }), { nil: undefined })
|
|
})
|
|
|
|
it('should pass all provided search parameters to the API', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
searchParamsArb,
|
|
async (params) => {
|
|
// Reset captured request
|
|
capturedRequest = null
|
|
|
|
// Call search with the generated params
|
|
await search(params)
|
|
|
|
// Verify the API was called
|
|
expect(post).toHaveBeenCalled()
|
|
|
|
// Get the captured request data
|
|
const callArgs = post.mock.calls[post.mock.calls.length - 1]
|
|
const [url, sentData] = callArgs
|
|
|
|
// Verify URL
|
|
expect(url).toBe('/users/search')
|
|
|
|
// Verify all defined parameters are passed through
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value !== undefined) {
|
|
expect(sentData[key]).toEqual(value)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should handle empty search parameters', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
fc.constant({}),
|
|
async (params) => {
|
|
await search(params)
|
|
|
|
expect(post).toHaveBeenCalledWith('/users/search', params)
|
|
return true
|
|
}
|
|
),
|
|
{ numRuns: 10 }
|
|
)
|
|
})
|
|
|
|
it('should preserve parameter types correctly', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
fc.record({
|
|
ageMin: fc.integer({ min: 18, max: 50 }),
|
|
ageMax: fc.integer({ min: 18, max: 50 }),
|
|
education: fc.array(fc.integer({ min: 1, max: 7 }), { minLength: 1, maxLength: 3 }),
|
|
pageIndex: fc.integer({ min: 1, max: 10 }),
|
|
pageSize: fc.integer({ min: 10, max: 50 })
|
|
}),
|
|
async (params) => {
|
|
await search(params)
|
|
|
|
const callArgs = post.mock.calls[post.mock.calls.length - 1]
|
|
const [, sentData] = callArgs
|
|
|
|
// Verify numeric types are preserved
|
|
expect(typeof sentData.ageMin).toBe('number')
|
|
expect(typeof sentData.ageMax).toBe('number')
|
|
expect(typeof sentData.pageIndex).toBe('number')
|
|
expect(typeof sentData.pageSize).toBe('number')
|
|
|
|
// Verify array type is preserved
|
|
expect(Array.isArray(sentData.education)).toBe(true)
|
|
|
|
return true
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('should ensure ageMin <= ageMax when both are provided', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
fc.integer({ min: 18, max: 50 }).chain(min =>
|
|
fc.record({
|
|
ageMin: fc.constant(min),
|
|
ageMax: fc.integer({ min: min, max: 50 })
|
|
})
|
|
),
|
|
async (params) => {
|
|
await search(params)
|
|
|
|
const callArgs = post.mock.calls[post.mock.calls.length - 1]
|
|
const [, sentData] = callArgs
|
|
|
|
// The API should receive the params as-is
|
|
// Validation of ageMin <= ageMax is a business rule that should be enforced
|
|
expect(sentData.ageMin).toBeLessThanOrEqual(sentData.ageMax)
|
|
|
|
return true
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
})
|
|
})
|