Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions backend/modules/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ pub mod chess960;
mod auth_tests;
pub mod config;
pub mod games;
pub mod player_stats;
// pub mod player_stats;
// ^ #1539's rating-history/online-count module is not compiled yet: it uses
// sqlx with a PgPool and table names that don't exist in this schema. Port
// it to db::DbPool + sea-orm (smdb.game) and register it in server.rs.
pub mod idempotency;
pub mod lc0;
pub mod metrics;
Expand All @@ -31,6 +34,5 @@ extern crate challenge;
// Re-export server module for external use
pub use auth::{login, logout, logout_all, refresh, register};
pub use idempotency::IdempotencyMiddleware;
.configure(crate::player_stats::configure)
pub use players::export_player_data;
pub use server::main;
6 changes: 5 additions & 1 deletion backend/modules/api/src/players.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use std::sync::Mutex;
use std::time::{Duration, Instant};
use validator::Validate;


use service::players::{
add_player as add_new_player, delete_player as delete_player_by_id,
find_player_by_id as get_single_player_by_id, get_player_preferences,
update_player as update_player_by_id, update_player_preferences,
};
use uuid::Uuid;

// ---------------------------------------------------------------------------
Expand Down
11 changes: 9 additions & 2 deletions backend/modules/pairing/src/swiss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ impl SwissPairer {
if !config.avoid_recent_opponents {
return false;
}
player_a.has_played_against(&candidate.id) || paired_ids.contains(&candidate.id)
// History may be recorded on either player, so check both sides.
player_a.has_played_against(&candidate.id)
|| candidate.has_played_against(&player_a.id)
|| paired_ids.contains(&candidate.id)
};

match remaining.iter().position(|(_, player)| !forbidden(player)) {
Expand Down Expand Up @@ -229,7 +232,11 @@ mod tests {
fn rematches_are_allowed_when_avoidance_is_disabled() {
let a = player("a", 2000);
let b = player("b", 1900).with_opponents(vec![a.id]);
let round = build_round(vec![a, b], PairingConfig::default());
let config = PairingConfig {
avoid_recent_opponents: false,
..PairingConfig::default()
};
let round = build_round(vec![a, b], config);
assert_eq!(round.pairings.len(), 1);
}

Expand Down
11 changes: 11 additions & 0 deletions frontend/__tests__/chessSocketSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,20 @@ describe("useChessSocket sync & offline queue (FE-113 / FE-114)", () => {
beforeEach(() => {
MockWebSocket.instances = [];
vi.stubGlobal("WebSocket", MockWebSocket as unknown as typeof WebSocket);
// Game sockets authenticate with a single-use ticket fetched with the
// signed-in user's access token (BE-103), so provide both.
localStorage.setItem("access_token", "test-access-token");
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ ticket: "single-use-ticket", expires_in: 60 }),
}),
);
});

afterEach(() => {
localStorage.removeItem("access_token");
vi.unstubAllGlobals();
globalThis.WebSocket = originalWebSocket;
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/__tests__/chessSocketUrl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe("useChessSocket URL (#FE-90)", () => {
vi.stubEnv("NEXT_PUBLIC_API_URL", "");

await expect(openSocketFor("abc")).resolves.toBe(
"ws://localhost:8000/v1/ws/game/abc?ticket=single-use-ticket",
"ws://localhost:8080/v1/ws/game/abc?ticket=single-use-ticket",
);
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/__tests__/csp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("buildConnectSrc (#FE-87)", () => {
it("defaults to the local backend when no API URL is set", () => {
const sources = buildConnectSrc({});

expect(sources).toEqual(expect.arrayContaining(["'self'", "http://localhost:8000", "ws://localhost:8000"]));
expect(sources).toEqual(expect.arrayContaining(["'self'", "http://localhost:8080", "ws://localhost:8080"]));
});

it("allows the third-party hosts the app calls", () => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/watch/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const useMockLiveGames =
export default function WatchPage() {
const [selectedGameId, setSelectedGameId] = useState<string | null>(null);
const [filter, setFilter] = useState<"all" | LiveGameMode>("all");
const [liveGames, setLiveGames] = useState<LiveGameRecord[]>(
const [liveGames] = useState<LiveGameRecord[]>(
useMockLiveGames ? (MOCK_LIVE_GAMES as LiveGameRecord[]) : []
);
const [loading, setLoading] = useState(!useMockLiveGames);
Expand Down
11 changes: 9 additions & 2 deletions frontend/components/__tests__/accessibility.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ import {
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import { AuthProvider } from "@/context/authContext";
import { GamePreferencesProvider } from "@/context/GamePreferencesContext";

// Board components read useGamePreferences(), which requires its provider.
// Board components read useGamePreferences(), whose provider syncs through
// useAuth(), so both providers are needed (same nesting as app/layout.tsx).
const Providers = ({ children }: { children: React.ReactNode }) => (
<AuthProvider>
<GamePreferencesProvider>{children}</GamePreferencesProvider>
</AuthProvider>
);
const render = (ui: React.ReactElement, options?: Parameters<typeof rtlRender>[1]) =>
rtlRender(ui, { wrapper: GamePreferencesProvider, ...options });
rtlRender(ui, { wrapper: Providers, ...options });
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { axe } from "vitest-axe";
import type { AxeMatchers } from "vitest-axe";
Expand Down
8 changes: 7 additions & 1 deletion frontend/context/walletContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,13 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
// 1) Try official @stellar/freighter-api package if available
try {
const freighterApi = await import("@stellar/freighter-api");
if (freighterApi && typeof freighterApi.signTransaction === "function") {
// Only use the package when the extension is actually present: without
// it, signTransaction() waits for an extension reply that never comes.
if (
freighterApi &&
typeof freighterApi.signTransaction === "function" &&
(await freighterApi.isConnected())
) {
const signed = await freighterApi.signTransaction(txXDR, {
networkPassphrase: NETWORK_PASSPHRASE,
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/lib/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function wsOriginOf(httpOrigin: string): string {
export function buildConnectSrc(env: CspEnv): string[] {
const sources = new Set<string>(["'self'"]);

for (const value of [env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000", env.NEXT_PUBLIC_BACKEND_URL]) {
for (const value of [env.NEXT_PUBLIC_API_URL ?? "http://localhost:8080", env.NEXT_PUBLIC_BACKEND_URL]) {
const origin = originOf(value);
if (origin) {
sources.add(origin);
Expand Down
Empty file modified frontend/scripts/generate-contract-bindings.sh
100644 → 100755
Empty file.
Loading