mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-04-19 05:18:42 -04:00
426 lines
13 KiB
TypeScript
426 lines
13 KiB
TypeScript
/**
|
|
* Tests for selector logic
|
|
* Tests all selectors from shared/selectors.ts
|
|
*/
|
|
|
|
import { BaseTest } from '../base/BaseTest.ts';
|
|
import { assertEquals, assert } from '@std/assert';
|
|
import {
|
|
selectors,
|
|
getSelector,
|
|
isValidSelector,
|
|
getAllSelectorIds
|
|
} from '../../../src/lib/shared/upgrades/selectors.ts';
|
|
|
|
interface MockItem {
|
|
id: number;
|
|
title: string;
|
|
dateAdded: string;
|
|
score: number;
|
|
popularity: number;
|
|
}
|
|
|
|
class SelectorsTest extends BaseTest {
|
|
private createMockItems(): MockItem[] {
|
|
return [
|
|
{
|
|
id: 1,
|
|
title: 'Movie A',
|
|
dateAdded: '2023-01-15T00:00:00Z',
|
|
score: 50,
|
|
popularity: 100
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Movie B',
|
|
dateAdded: '2023-06-20T00:00:00Z',
|
|
score: 75,
|
|
popularity: 200
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'Movie C',
|
|
dateAdded: '2022-03-10T00:00:00Z',
|
|
score: 25,
|
|
popularity: 150
|
|
},
|
|
{
|
|
id: 4,
|
|
title: 'Movie D',
|
|
dateAdded: '2024-01-01T00:00:00Z',
|
|
score: 100,
|
|
popularity: 50
|
|
},
|
|
{
|
|
id: 5,
|
|
title: 'Movie E',
|
|
dateAdded: '2023-09-05T00:00:00Z',
|
|
score: 60,
|
|
popularity: 300
|
|
}
|
|
];
|
|
}
|
|
|
|
runTests(): void {
|
|
// =====================
|
|
// Helper Function Tests
|
|
// =====================
|
|
|
|
this.test('getSelector returns correct selector', () => {
|
|
const selector = getSelector('random');
|
|
assertEquals(selector?.id, 'random');
|
|
assertEquals(selector?.label, 'Random');
|
|
});
|
|
|
|
this.test('getSelector returns undefined for invalid id', () => {
|
|
const selector = getSelector('nonexistent');
|
|
assertEquals(selector, undefined);
|
|
});
|
|
|
|
this.test('isValidSelector returns true for valid ids', () => {
|
|
assertEquals(isValidSelector('random'), true);
|
|
assertEquals(isValidSelector('oldest'), true);
|
|
assertEquals(isValidSelector('newest'), true);
|
|
assertEquals(isValidSelector('lowest_score'), true);
|
|
assertEquals(isValidSelector('most_popular'), true);
|
|
assertEquals(isValidSelector('least_popular'), true);
|
|
});
|
|
|
|
this.test('isValidSelector returns false for invalid ids', () => {
|
|
assertEquals(isValidSelector('nonexistent'), false);
|
|
assertEquals(isValidSelector(''), false);
|
|
});
|
|
|
|
this.test('getAllSelectorIds returns all selector ids', () => {
|
|
const ids = getAllSelectorIds();
|
|
assertEquals(ids.length, 8);
|
|
assert(ids.includes('random'));
|
|
assert(ids.includes('oldest'));
|
|
assert(ids.includes('newest'));
|
|
assert(ids.includes('lowest_score'));
|
|
assert(ids.includes('most_popular'));
|
|
assert(ids.includes('least_popular'));
|
|
assert(ids.includes('alphabetical_asc'));
|
|
assert(ids.includes('alphabetical_desc'));
|
|
});
|
|
|
|
// =====================
|
|
// Random Selector
|
|
// =====================
|
|
|
|
this.test('random: selects correct count', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('random')!;
|
|
const selected = selector.select(items, 3);
|
|
assertEquals(selected.length, 3);
|
|
});
|
|
|
|
this.test('random: handles count larger than items', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('random')!;
|
|
const selected = selector.select(items, 10);
|
|
assertEquals(selected.length, 5); // Only 5 items available
|
|
});
|
|
|
|
this.test('random: returns empty array for empty input', () => {
|
|
const selector = getSelector('random')!;
|
|
const selected = selector.select([], 5);
|
|
assertEquals(selected.length, 0);
|
|
});
|
|
|
|
this.test('random: does not modify original array', () => {
|
|
const items = this.createMockItems();
|
|
const originalFirst = items[0];
|
|
const selector = getSelector('random')!;
|
|
selector.select(items, 3);
|
|
assertEquals(items[0], originalFirst);
|
|
assertEquals(items.length, 5);
|
|
});
|
|
|
|
// =====================
|
|
// Oldest Selector
|
|
// =====================
|
|
|
|
this.test('oldest: selects oldest items first', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('oldest')!;
|
|
const selected = selector.select(items, 3);
|
|
|
|
assertEquals(selected.length, 3);
|
|
// Movie C (2022-03-10) is oldest
|
|
assertEquals(selected[0].id, 3);
|
|
assertEquals(selected[0].title, 'Movie C');
|
|
// Movie A (2023-01-15) is second oldest
|
|
assertEquals(selected[1].id, 1);
|
|
// Movie B (2023-06-20) is third oldest
|
|
assertEquals(selected[2].id, 2);
|
|
});
|
|
|
|
this.test('oldest: handles items with same date', () => {
|
|
const items: MockItem[] = [
|
|
{ id: 1, title: 'A', dateAdded: '2023-01-15T00:00:00Z', score: 0, popularity: 0 },
|
|
{ id: 2, title: 'B', dateAdded: '2023-01-15T00:00:00Z', score: 0, popularity: 0 },
|
|
{ id: 3, title: 'C', dateAdded: '2022-01-15T00:00:00Z', score: 0, popularity: 0 }
|
|
];
|
|
const selector = getSelector('oldest')!;
|
|
const selected = selector.select(items, 2);
|
|
|
|
assertEquals(selected.length, 2);
|
|
assertEquals(selected[0].id, 3); // Oldest first
|
|
});
|
|
|
|
this.test('oldest: handles missing dateAdded', () => {
|
|
const items = [
|
|
{ id: 1, title: 'A', score: 0, popularity: 0 },
|
|
{ id: 2, title: 'B', dateAdded: '2023-01-15T00:00:00Z', score: 0, popularity: 0 }
|
|
] as MockItem[];
|
|
const selector = getSelector('oldest')!;
|
|
const selected = selector.select(items, 2);
|
|
|
|
assertEquals(selected.length, 2);
|
|
// Item without dateAdded should be treated as epoch (very old)
|
|
assertEquals(selected[0].id, 1);
|
|
});
|
|
|
|
// =====================
|
|
// Newest Selector
|
|
// =====================
|
|
|
|
this.test('newest: selects newest items first', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('newest')!;
|
|
const selected = selector.select(items, 3);
|
|
|
|
assertEquals(selected.length, 3);
|
|
// Movie D (2024-01-01) is newest
|
|
assertEquals(selected[0].id, 4);
|
|
assertEquals(selected[0].title, 'Movie D');
|
|
// Movie E (2023-09-05) is second newest
|
|
assertEquals(selected[1].id, 5);
|
|
// Movie B (2023-06-20) is third newest
|
|
assertEquals(selected[2].id, 2);
|
|
});
|
|
|
|
this.test('newest: handles count of 1', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('newest')!;
|
|
const selected = selector.select(items, 1);
|
|
|
|
assertEquals(selected.length, 1);
|
|
assertEquals(selected[0].id, 4); // Movie D is newest
|
|
});
|
|
|
|
// =====================
|
|
// Lowest Score Selector
|
|
// =====================
|
|
|
|
this.test('lowest_score: selects items with lowest score first', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('lowest_score')!;
|
|
const selected = selector.select(items, 3);
|
|
|
|
assertEquals(selected.length, 3);
|
|
// Movie C (score: 25) has lowest score
|
|
assertEquals(selected[0].id, 3);
|
|
assertEquals(selected[0].score, 25);
|
|
// Movie A (score: 50) has second lowest
|
|
assertEquals(selected[1].id, 1);
|
|
assertEquals(selected[1].score, 50);
|
|
// Movie E (score: 60) has third lowest
|
|
assertEquals(selected[2].id, 5);
|
|
assertEquals(selected[2].score, 60);
|
|
});
|
|
|
|
this.test('lowest_score: handles zero scores', () => {
|
|
const items: MockItem[] = [
|
|
{ id: 1, title: 'A', dateAdded: '', score: 0, popularity: 0 },
|
|
{ id: 2, title: 'B', dateAdded: '', score: 50, popularity: 0 },
|
|
{ id: 3, title: 'C', dateAdded: '', score: -10, popularity: 0 } // Negative score
|
|
];
|
|
const selector = getSelector('lowest_score')!;
|
|
const selected = selector.select(items, 2);
|
|
|
|
assertEquals(selected.length, 2);
|
|
assertEquals(selected[0].id, 3); // Negative is lowest
|
|
assertEquals(selected[1].id, 1); // Zero is next
|
|
});
|
|
|
|
this.test('lowest_score: handles missing score', () => {
|
|
const items = [
|
|
{ id: 1, title: 'A', dateAdded: '', popularity: 0 },
|
|
{ id: 2, title: 'B', dateAdded: '', score: 50, popularity: 0 }
|
|
] as MockItem[];
|
|
const selector = getSelector('lowest_score')!;
|
|
const selected = selector.select(items, 2);
|
|
|
|
assertEquals(selected.length, 2);
|
|
// Item without score (treated as 0) should be first
|
|
assertEquals(selected[0].id, 1);
|
|
});
|
|
|
|
// =====================
|
|
// Most Popular Selector
|
|
// =====================
|
|
|
|
this.test('most_popular: selects most popular items first', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('most_popular')!;
|
|
const selected = selector.select(items, 3);
|
|
|
|
assertEquals(selected.length, 3);
|
|
// Movie E (popularity: 300) is most popular
|
|
assertEquals(selected[0].id, 5);
|
|
assertEquals(selected[0].popularity, 300);
|
|
// Movie B (popularity: 200) is second
|
|
assertEquals(selected[1].id, 2);
|
|
assertEquals(selected[1].popularity, 200);
|
|
// Movie C (popularity: 150) is third
|
|
assertEquals(selected[2].id, 3);
|
|
assertEquals(selected[2].popularity, 150);
|
|
});
|
|
|
|
this.test('most_popular: handles same popularity', () => {
|
|
const items: MockItem[] = [
|
|
{ id: 1, title: 'A', dateAdded: '', score: 0, popularity: 100 },
|
|
{ id: 2, title: 'B', dateAdded: '', score: 0, popularity: 100 },
|
|
{ id: 3, title: 'C', dateAdded: '', score: 0, popularity: 200 }
|
|
];
|
|
const selector = getSelector('most_popular')!;
|
|
const selected = selector.select(items, 2);
|
|
|
|
assertEquals(selected.length, 2);
|
|
assertEquals(selected[0].id, 3); // Most popular first
|
|
});
|
|
|
|
// =====================
|
|
// Least Popular Selector
|
|
// =====================
|
|
|
|
this.test('least_popular: selects least popular items first', () => {
|
|
const items = this.createMockItems();
|
|
const selector = getSelector('least_popular')!;
|
|
const selected = selector.select(items, 3);
|
|
|
|
assertEquals(selected.length, 3);
|
|
// Movie D (popularity: 50) is least popular
|
|
assertEquals(selected[0].id, 4);
|
|
assertEquals(selected[0].popularity, 50);
|
|
// Movie A (popularity: 100) is second least
|
|
assertEquals(selected[1].id, 1);
|
|
assertEquals(selected[1].popularity, 100);
|
|
// Movie C (popularity: 150) is third least
|
|
assertEquals(selected[2].id, 3);
|
|
assertEquals(selected[2].popularity, 150);
|
|
});
|
|
|
|
this.test('least_popular: handles missing popularity', () => {
|
|
const items = [
|
|
{ id: 1, title: 'A', dateAdded: '', score: 0 },
|
|
{ id: 2, title: 'B', dateAdded: '', score: 0, popularity: 100 }
|
|
] as MockItem[];
|
|
const selector = getSelector('least_popular')!;
|
|
const selected = selector.select(items, 2);
|
|
|
|
assertEquals(selected.length, 2);
|
|
// Item without popularity (treated as 0) should be first (least popular)
|
|
assertEquals(selected[0].id, 1);
|
|
});
|
|
|
|
// =====================
|
|
// Edge Cases
|
|
// =====================
|
|
|
|
this.test('edge case: count of 0 returns empty array', () => {
|
|
const items = this.createMockItems();
|
|
for (const selector of selectors) {
|
|
const selected = selector.select(items, 0);
|
|
assertEquals(selected.length, 0, `${selector.id} should return empty array for count=0`);
|
|
}
|
|
});
|
|
|
|
this.test('edge case: negative count returns empty array', () => {
|
|
const items = this.createMockItems();
|
|
for (const selector of selectors) {
|
|
const selected = selector.select(items, -5);
|
|
assertEquals(
|
|
selected.length,
|
|
0,
|
|
`${selector.id} should return empty array for negative count`
|
|
);
|
|
}
|
|
});
|
|
|
|
this.test('edge case: single item', () => {
|
|
const items: MockItem[] = [
|
|
{ id: 1, title: 'Only One', dateAdded: '2023-01-15T00:00:00Z', score: 50, popularity: 100 }
|
|
];
|
|
|
|
for (const selector of selectors) {
|
|
const selected = selector.select(items, 1);
|
|
assertEquals(selected.length, 1, `${selector.id} should return 1 item`);
|
|
assertEquals(selected[0].id, 1, `${selector.id} should return the only item`);
|
|
}
|
|
});
|
|
|
|
// =====================
|
|
// Integration Scenarios
|
|
// =====================
|
|
|
|
this.test('scenario: upgrade workflow with lowest score selector', () => {
|
|
// Simulate items that passed filter evaluation
|
|
const filteredItems: MockItem[] = [
|
|
{
|
|
id: 10,
|
|
title: 'Needs Upgrade',
|
|
dateAdded: '2023-01-01T00:00:00Z',
|
|
score: 30,
|
|
popularity: 100
|
|
},
|
|
{
|
|
id: 20,
|
|
title: 'Almost There',
|
|
dateAdded: '2023-02-01T00:00:00Z',
|
|
score: 70,
|
|
popularity: 200
|
|
},
|
|
{
|
|
id: 30,
|
|
title: 'Very Low',
|
|
dateAdded: '2023-03-01T00:00:00Z',
|
|
score: 10,
|
|
popularity: 150
|
|
},
|
|
{ id: 40, title: 'Medium', dateAdded: '2023-04-01T00:00:00Z', score: 50, popularity: 80 }
|
|
];
|
|
|
|
const selector = getSelector('lowest_score')!;
|
|
const toUpgrade = selector.select(filteredItems, 2);
|
|
|
|
assertEquals(toUpgrade.length, 2);
|
|
// Should select the two with lowest scores for upgrade
|
|
assertEquals(toUpgrade[0].id, 30); // score: 10
|
|
assertEquals(toUpgrade[1].id, 10); // score: 30
|
|
});
|
|
|
|
this.test('scenario: upgrade workflow with oldest selector', () => {
|
|
// Simulate prioritizing oldest items that need upgrade
|
|
const filteredItems: MockItem[] = [
|
|
{ id: 10, title: 'Recent', dateAdded: '2024-01-01T00:00:00Z', score: 30, popularity: 100 },
|
|
{ id: 20, title: 'Old', dateAdded: '2020-01-01T00:00:00Z', score: 40, popularity: 200 },
|
|
{ id: 30, title: 'Very Old', dateAdded: '2018-06-01T00:00:00Z', score: 50, popularity: 150 }
|
|
];
|
|
|
|
const selector = getSelector('oldest')!;
|
|
const toUpgrade = selector.select(filteredItems, 2);
|
|
|
|
assertEquals(toUpgrade.length, 2);
|
|
assertEquals(toUpgrade[0].id, 30); // Very Old (2018)
|
|
assertEquals(toUpgrade[1].id, 20); // Old (2020)
|
|
});
|
|
}
|
|
}
|
|
|
|
// Create instance and run tests
|
|
const selectorsTest = new SelectorsTest();
|
|
selectorsTest.runTests();
|