make tests passing in cicd
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -29,5 +29,8 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: bun run build
|
run: bun run build
|
||||||
|
|
||||||
|
- name: Install Playwright browsers
|
||||||
|
run: bunx playwright install --with-deps chromium
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: bun test
|
run: bun run test
|
||||||
|
|||||||
37
e2e/smoke.test.ts
Normal file
37
e2e/smoke.test.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Smoke Tests', () => {
|
||||||
|
test('landing page loads with sign-in link', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.getByRole('heading', { name: /Vash Esports/i })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('banner').getByRole('link', { name: 'Sign in with osu!' })
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('protected API returns 401 without auth', async ({ request }) => {
|
||||||
|
const response = await request.get('/api/matches');
|
||||||
|
expect(response.status()).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('health endpoint responds', async ({ request }) => {
|
||||||
|
const response = await request.get('/api/health');
|
||||||
|
// 200 if DB is up, 503 if not — both are valid from the endpoint
|
||||||
|
expect([200, 503]).toContain(response.status());
|
||||||
|
const body = await response.json();
|
||||||
|
expect(body).toHaveProperty('status');
|
||||||
|
expect(body).toHaveProperty('timestamp');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('leaderboard page renders', async ({ page }) => {
|
||||||
|
await page.goto('/leaderboard');
|
||||||
|
await expect(page.locator('h1')).toContainText('Leaderboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('privacy and terms pages render', async ({ page }) => {
|
||||||
|
for (const path of ['/privacy', '/terms']) {
|
||||||
|
const response = await page.goto(path);
|
||||||
|
expect(response?.ok()).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,35 +1,40 @@
|
|||||||
import { beforeEach, describe, expect, test, mock } from 'bun:test';
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
import { MATCH_STATES } from './types';
|
import { MATCH_STATES } from './types';
|
||||||
import { pickMap } from './engine';
|
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
db: {
|
db: {
|
||||||
query: {
|
query: {
|
||||||
match: { findFirst: mock() },
|
match: { findFirst: vi.fn() },
|
||||||
matchParticipant: { findMany: mock() },
|
matchParticipant: { findMany: vi.fn() },
|
||||||
matchGame: { findMany: mock() },
|
matchGame: { findMany: vi.fn() },
|
||||||
mappoolSlot: { findFirst: mock() }
|
mappoolSlot: { findFirst: vi.fn() }
|
||||||
},
|
},
|
||||||
insert: mock(),
|
insert: vi.fn(),
|
||||||
update: mock(),
|
update: vi.fn(),
|
||||||
delete: mock(),
|
delete: vi.fn(),
|
||||||
select: mock()
|
select: vi.fn()
|
||||||
|
},
|
||||||
|
helpers: {
|
||||||
|
getMatchOrThrow: vi.fn(),
|
||||||
|
getExpectedPicker: vi.fn(),
|
||||||
|
assertState: vi.fn(),
|
||||||
|
getMatchFull: vi.fn()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mock.module('$lib/server/db', () => ({ db: mocks.db }));
|
vi.mock('$lib/server/db', () => ({ db: mocks.db }));
|
||||||
|
|
||||||
mock.module('drizzle-orm', () => ({
|
vi.mock('drizzle-orm', () => ({
|
||||||
eq: mock(() => ({})),
|
eq: vi.fn(() => ({})),
|
||||||
and: mock(() => ({})),
|
and: vi.fn(() => ({})),
|
||||||
asc: mock(() => ({})),
|
asc: vi.fn(() => ({})),
|
||||||
lt: mock(() => ({})),
|
lt: vi.fn(() => ({})),
|
||||||
inArray: mock(() => ({})),
|
inArray: vi.fn(() => ({})),
|
||||||
sql: mock(() => ({})),
|
sql: vi.fn(() => ({})),
|
||||||
isNotNull: mock(() => ({}))
|
isNotNull: vi.fn(() => ({}))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mock.module('$lib/server/db/schema', () => ({
|
vi.mock('$lib/server/db/schema', () => ({
|
||||||
match: {},
|
match: {},
|
||||||
matchParticipant: {},
|
matchParticipant: {},
|
||||||
matchParticipantPlayer: {},
|
matchParticipantPlayer: {},
|
||||||
@@ -41,40 +46,54 @@ mock.module('$lib/server/db/schema', () => ({
|
|||||||
teamMember: {}
|
teamMember: {}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mock.module('$lib/server/db/auth.schema', () => ({ account: {} }));
|
vi.mock('$lib/server/db/auth.schema', () => ({ account: {} }));
|
||||||
|
|
||||||
mock.module('$lib/server/discord/client', () => ({
|
vi.mock('$lib/server/discord/client', () => ({
|
||||||
notifyMatchCreated: mock(),
|
notifyMatchCreated: vi.fn(),
|
||||||
notifyMatchFinished: mock()
|
notifyMatchFinished: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mock.module('$lib/server/osu/api', () => ({
|
vi.mock('$lib/server/osu/api', () => ({
|
||||||
getUser: mock()
|
getUser: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('./helpers', () => ({
|
||||||
|
getExpectedPicker: (...args: unknown[]) => mocks.helpers.getExpectedPicker(...args),
|
||||||
|
getMatchOrThrow: (...args: unknown[]) => mocks.helpers.getMatchOrThrow(...args),
|
||||||
|
assertState: (...args: unknown[]) => mocks.helpers.assertState(...args),
|
||||||
|
getMatchFull: (...args: unknown[]) => mocks.helpers.getMatchFull(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./rating', () => ({
|
||||||
|
updateElo: vi.fn(),
|
||||||
|
calculateInitialElo: vi.fn(),
|
||||||
|
selectMappoolForRating: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { pickMap } = await import('./engine');
|
||||||
|
|
||||||
describe('pickMap', () => {
|
describe('pickMap', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocks.db.query.match.findFirst.mockReset();
|
vi.clearAllMocks();
|
||||||
mocks.db.query.matchParticipant.findMany.mockReset();
|
|
||||||
mocks.db.query.matchGame.findMany.mockReset();
|
|
||||||
mocks.db.query.mappoolSlot.findFirst.mockReset();
|
|
||||||
mocks.db.insert.mockReset();
|
|
||||||
mocks.db.update.mockReset();
|
|
||||||
mocks.db.delete.mockReset();
|
|
||||||
mocks.db.select.mockReset();
|
|
||||||
|
|
||||||
mocks.db.query.match.findFirst.mockResolvedValue({
|
// getMatchOrThrow returns a match object
|
||||||
|
mocks.helpers.getMatchOrThrow.mockResolvedValue({
|
||||||
id: 'match-1',
|
id: 'match-1',
|
||||||
state: MATCH_STATES.PICKING,
|
state: MATCH_STATES.PICKING,
|
||||||
config: { bestOf: 5, teamSize: 1, scoringType: 'score_v2' }
|
config: { bestOf: 5, teamSize: 1, scoringType: 'score_v2' }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// db.query.matchParticipant.findMany returns participants
|
||||||
mocks.db.query.matchParticipant.findMany.mockResolvedValue([
|
mocks.db.query.matchParticipant.findMany.mockResolvedValue([
|
||||||
{ id: 'p1', pickOrder: 1, score: 1 },
|
{ id: 'p1', pickOrder: 1, score: 1 },
|
||||||
{ id: 'p2', pickOrder: 2, score: 1 }
|
{ id: 'p2', pickOrder: 2, score: 1 }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// db.query.matchGame.findMany returns no games played yet
|
||||||
mocks.db.query.matchGame.findMany.mockResolvedValue([]);
|
mocks.db.query.matchGame.findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
|
// getExpectedPicker returns p1 (first picker)
|
||||||
|
mocks.helpers.getExpectedPicker.mockReturnValue({ id: 'p1', pickOrder: 1, score: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws when a non-current picker tries to pick', async () => {
|
test('throws when a non-current picker tries to pick', async () => {
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import { beforeEach, describe, expect, test, mock } from 'bun:test';
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
import { MATCH_STATES } from './types';
|
import { MATCH_STATES } from './types';
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
db: {
|
db: {
|
||||||
query: {
|
query: {
|
||||||
matchParticipantPlayer: { findMany: mock() },
|
matchParticipantPlayer: { findMany: vi.fn() },
|
||||||
playerRating: { findFirst: mock() },
|
playerRating: { findFirst: vi.fn() },
|
||||||
matchQueue: { findFirst: mock() }
|
matchQueue: { findFirst: vi.fn() }
|
||||||
},
|
},
|
||||||
delete: mock()
|
delete: vi.fn()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mock.module('$lib/server/db', () => ({ db: mocks.db }));
|
vi.mock('$lib/server/db', () => ({ db: mocks.db }));
|
||||||
|
|
||||||
mock.module('drizzle-orm', () => ({
|
vi.mock('drizzle-orm', () => ({
|
||||||
eq: mock(() => ({})),
|
eq: vi.fn(() => ({})),
|
||||||
and: mock(() => ({})),
|
and: vi.fn(() => ({})),
|
||||||
asc: mock(() => ({})),
|
asc: vi.fn(() => ({})),
|
||||||
lt: mock(() => ({})),
|
lt: vi.fn(() => ({})),
|
||||||
inArray: mock(() => ({})),
|
inArray: vi.fn(() => ({})),
|
||||||
sql: mock(() => ({})),
|
sql: vi.fn(() => ({})),
|
||||||
isNotNull: mock(() => ({}))
|
isNotNull: vi.fn(() => ({}))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mock.module('$lib/server/db/schema', () => ({
|
vi.mock('$lib/server/db/schema', () => ({
|
||||||
match: {},
|
match: {},
|
||||||
matchQueue: {},
|
matchQueue: {},
|
||||||
matchParticipantPlayer: {},
|
matchParticipantPlayer: {},
|
||||||
playerRating: {}
|
playerRating: {}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mock.module('$lib/server/db/auth.schema', () => ({ account: {} }));
|
vi.mock('$lib/server/db/auth.schema', () => ({ account: {} }));
|
||||||
|
|
||||||
mock.module('$lib/server/osu/api', () => ({
|
vi.mock('$lib/server/osu/api', () => ({
|
||||||
getUser: mock()
|
getUser: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mock.module('$lib/server/match/engine', () => ({
|
vi.mock('$lib/server/match/engine', () => ({
|
||||||
createMatch: mock()
|
createMatch: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { joinQueue } = await import('./queue');
|
const { joinQueue } = await import('./queue');
|
||||||
@@ -53,7 +53,7 @@ describe('joinQueue', () => {
|
|||||||
|
|
||||||
test('throws when user is already in an active match', async () => {
|
test('throws when user is already in an active match', async () => {
|
||||||
mocks.db.delete.mockReturnValue({
|
mocks.db.delete.mockReturnValue({
|
||||||
where: mock(() => ({ returning: mock(() => Promise.resolve([])) }))
|
where: vi.fn(() => ({ returning: vi.fn(() => Promise.resolve([])) }))
|
||||||
});
|
});
|
||||||
|
|
||||||
mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([
|
mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([
|
||||||
@@ -72,7 +72,7 @@ describe('joinQueue', () => {
|
|||||||
|
|
||||||
test('throws when user has no rating', async () => {
|
test('throws when user has no rating', async () => {
|
||||||
mocks.db.delete.mockReturnValue({
|
mocks.db.delete.mockReturnValue({
|
||||||
where: mock(() => ({ returning: mock(() => Promise.resolve([])) }))
|
where: vi.fn(() => ({ returning: vi.fn(() => Promise.resolve([])) }))
|
||||||
});
|
});
|
||||||
|
|
||||||
mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([]);
|
mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([]);
|
||||||
@@ -85,7 +85,7 @@ describe('joinQueue', () => {
|
|||||||
|
|
||||||
test('throws when user is already in queue', async () => {
|
test('throws when user is already in queue', async () => {
|
||||||
mocks.db.delete.mockReturnValue({
|
mocks.db.delete.mockReturnValue({
|
||||||
where: mock(() => ({ returning: mock(() => Promise.resolve([])) }))
|
where: vi.fn(() => ({ returning: vi.fn(() => Promise.resolve([])) }))
|
||||||
});
|
});
|
||||||
|
|
||||||
mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([]);
|
mocks.db.query.matchParticipantPlayer.findMany.mockResolvedValue([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user