UNPKG

unofficial-ravensburger-playhub-mcp

Version:
515 lines (514 loc) 20.3 kB
import { describe, it } from "node:test"; import assert from "node:assert"; import { formatStore, formatEvent, formatMatchEntry, formatStandingEntry, formatRegistrationEntry, formatLeaderboardEntry, formatLeaderboard, parseRecordToWinsLosses, } from "../lib/formatters.js"; describe("formatStore", () => { it("formats minimal store with name and id", () => { const gameStore = { id: "gs1", store: { id: 1, name: "Test Store" }, store_types: [], store_types_pretty: [], }; const out = formatStore(gameStore); assert.ok(out.includes("**Test Store**")); assert.ok(out.includes("Store ID: 1")); assert.ok(out.includes("Game Store ID: gs1")); }); it("includes address, phone, email, website when present", () => { const gameStore = { id: "gs1", store: { id: 1, name: "Full Store", full_address: "123 Main St", phone_number: "555-1234", email: "store@example.com", website: "https://store.example.com", }, store_types: [], store_types_pretty: [], }; const out = formatStore(gameStore); assert.ok(out.includes("📍 123 Main St")); assert.ok(out.includes("📞 555-1234")); assert.ok(out.includes("📧 store@example.com")); assert.ok(out.includes("🌐 https://store.example.com")); }); it("includes store_types_pretty when present", () => { const gameStore = { id: "gs1", store: { id: 1, name: "Store" }, store_types: ["flgs"], store_types_pretty: ["Friendly Local Game Store"], }; const out = formatStore(gameStore); assert.ok(out.includes("🏷️ Types: Friendly Local Game Store")); }); it("includes bio when present", () => { const gameStore = { id: "gs1", store: { id: 1, name: "Store", bio: "We sell cards." }, store_types: [], store_types_pretty: [], }; const out = formatStore(gameStore); assert.ok(out.includes("We sell cards.")); }); it("includes social links when present", () => { const gameStore = { id: "gs1", store: { id: 1, name: "Store", discord_url: "https://discord.gg/abc", facebook_url: "https://facebook.com/store", instagram_handle: "store", twitter_handle: "store", }, store_types: [], store_types_pretty: [], }; const out = formatStore(gameStore); assert.ok(out.includes("Discord: https://discord.gg/abc")); assert.ok(out.includes("Facebook: https://facebook.com/store")); assert.ok(out.includes("Instagram: @store")); assert.ok(out.includes("Twitter: @store")); }); }); describe("formatEvent", () => { it("formats minimal event with name and id", () => { const event = { id: 100, name: "Weekly League", start_datetime: "2025-02-01T18:00:00Z", }; const out = formatEvent(event); assert.ok(out.includes("**Weekly League**")); assert.ok(out.includes("ID: 100")); assert.ok(out.includes("📅")); }); it("includes format, category, store, address when present", () => { const event = { id: 101, name: "Event", start_datetime: "2025-02-01T18:00:00Z", gameplay_format: { id: "f1", name: "Constructed" }, event_configuration_template: "template-id", store: { id: 1, name: "Game Store" }, full_address: "456 Oak Ave", }; const out = formatEvent(event); assert.ok(out.includes("🎮 Format: Constructed")); assert.ok(out.includes("📁 Category:")); assert.ok(out.includes("🏪 Store: Game Store")); assert.ok(out.includes("📍 456 Oak Ave")); }); it("includes distance when present", () => { const event = { id: 102, name: "Event", start_datetime: "2025-02-01T18:00:00Z", distance_in_miles: 5.5, }; const out = formatEvent(event); assert.ok(out.includes("🚗 Distance: 5.5 miles")); }); it("shows paid entry when cost_in_cents > 0", () => { const event = { id: 103, name: "Event", start_datetime: "2025-02-01T18:00:00Z", cost_in_cents: 1500, currency: "USD", }; const out = formatEvent(event); assert.ok(out.includes("💰 Entry: USD $15.00")); }); it("shows free entry when cost_in_cents is 0", () => { const event = { id: 104, name: "Event", start_datetime: "2025-02-01T18:00:00Z", cost_in_cents: 0, }; const out = formatEvent(event); assert.ok(out.includes("💰 Entry: Free")); }); it("shows participants when capacity present", () => { const event = { id: 105, name: "Event", start_datetime: "2025-02-01T18:00:00Z", capacity: 32, registered_user_count: 20, }; const out = formatEvent(event); assert.ok(out.includes("👥 Participants: 20/32")); }); it("shows registered count when no capacity", () => { const event = { id: 106, name: "Event", start_datetime: "2025-02-01T18:00:00Z", registered_user_count: 8, }; const out = formatEvent(event); assert.ok(out.includes("👥 Registered: 8")); }); it("includes display_status and settings when present", () => { const event = { id: 107, name: "Event", start_datetime: "2025-02-01T18:00:00Z", display_status: "Upcoming", settings: { event_lifecycle_status: "REGISTRATION_OPEN" }, }; const out = formatEvent(event); assert.ok(out.includes("📊 Status: Upcoming")); assert.ok(out.includes("🎟️ Registration: registration open")); }); it("includes featured and online flags when true", () => { const event = { id: 108, name: "Event", start_datetime: "2025-02-01T18:00:00Z", is_headlining_event: true, event_is_online: true, }; const out = formatEvent(event); assert.ok(out.includes("⭐ Featured Event")); assert.ok(out.includes("🌐 Online Event")); }); it("includes description when present", () => { const event = { id: 109, name: "Event", start_datetime: "2025-02-01T18:00:00Z", description: "Bring your deck!", }; const out = formatEvent(event); assert.ok(out.includes("Bring your deck!")); }); it("includes tournament round IDs when tournament_phases present", () => { const event = { id: 110, name: "Store Champs", start_datetime: "2025-02-01T18:00:00Z", tournament_phases: [ { id: 1, phase_name: "Phase 1", status: "COMPLETE", rounds: [ { id: 415064, round_number: 1, status: "COMPLETE" }, { id: 415067, round_number: 4, status: "COMPLETE" }, ], }, ], }; const out = formatEvent(event); assert.ok(out.includes("Tournament rounds")); assert.ok(out.includes("get_tournament_round_standings")); assert.ok(out.includes("415064")); assert.ok(out.includes("415067")); }); }); describe("formatMatchEntry", () => { it("formats completed match with winner and score", () => { const match = { table_number: 1, status: "COMPLETE", winning_player: 15124, games_won_by_winner: 2, games_won_by_loser: 1, player_match_relationships: [ { player_order: 1, player: { id: 6679, best_identifier: "Rachel Z" } }, { player_order: 2, player: { id: 15124, best_identifier: "Joseph C" } }, ], }; const out = formatMatchEntry(match, 0); assert.ok(out.includes("Rachel Z vs Joseph C")); assert.ok(out.includes("Joseph C wins")); assert.ok(out.includes("2-1")); }); it("prefers user_event_status.best_identifier for pairings and winner (display name)", () => { const match = { table_number: 1, status: "COMPLETE", winning_player: 15124, games_won_by_winner: 2, games_won_by_loser: 1, player_match_relationships: [ { player_order: 1, player: { id: 6679, best_identifier: "Rachel Z" }, user_event_status: { best_identifier: "Rachel Zammitt" }, }, { player_order: 2, player: { id: 15124, best_identifier: "Joseph C" }, user_event_status: { best_identifier: "JC22" }, }, ], }; const out = formatMatchEntry(match, 0); assert.ok(out.includes("Rachel Zammitt vs JC22")); assert.ok(out.includes("JC22 wins")); }); it("formats bye match", () => { const match = { table_number: 2, match_is_bye: true, player_match_relationships: [ { player_order: 1, player: { best_identifier: "Alice" } }, ], }; const out = formatMatchEntry(match, 0); assert.ok(out.includes("Bye")); }); it("formats draw", () => { const match = { match_is_intentional_draw: true, player_match_relationships: [ { player_order: 1, player: { best_identifier: "A" } }, { player_order: 2, player: { best_identifier: "B" } }, ], }; const out = formatMatchEntry(match, 0); assert.ok(out.includes("A vs B")); assert.ok(out.includes("Draw")); }); it("falls back to status when no winner", () => { const match = { status: "IN_PROGRESS", player_match_relationships: [ { player_order: 1, player: { best_identifier: "X" } }, { player_order: 2, player: { best_identifier: "Y" } }, ], }; const out = formatMatchEntry(match, 0); assert.ok(out.includes("X vs Y")); assert.ok(out.includes("IN_PROGRESS")); }); }); describe("formatStandingEntry", () => { it("falls back to username when player_name and display_name missing", () => { const entry = { username: "player1" }; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("1. player1")); }); it("uses player.best_identifier when present (API shape)", () => { const entry = { player: { best_identifier: "Corey J" }, rank: 1 }; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("1. Corey J")); }); it("prefers user_event_status.best_identifier over player.best_identifier (display name)", () => { const entry = { rank: 1, player: { best_identifier: "Joseph C" }, user_event_status: { best_identifier: "JC22" }, }; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("1. JC22")); }); it("uses em dash when no name fields", () => { const entry = {}; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("1. —")); }); it("shows record with only wins (losses 0)", () => { const entry = { player_name: "A", wins: 3 }; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("Record: 3-0")); }); it("shows record from record/match_record (API shape)", () => { const entry = { player: { best_identifier: "X" }, record: "3-0-1", match_points: 10 }; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("Record: 3-0-1")); assert.ok(out.includes("Match points: 10")); }); it("shows OMWP/GWP from opponent_match_win_percentage and game_win_percentage", () => { const entry = { player_name: "Y", opponent_match_win_percentage: 0.645, game_win_percentage: 0.636, }; const out = formatStandingEntry(entry, 0); assert.ok(out.includes("OMWP: 64.5%")); assert.ok(out.includes("GWP: 63.6%")); }); }); describe("formatRegistrationEntry", () => { it("falls back to em dash when no name available", () => { const entry = {}; const out = formatRegistrationEntry(entry, 0); assert.ok(out.includes("1. —")); }); it("uses best_identifier when present (API shape)", () => { const entry = { best_identifier: "Corey J", user: { best_identifier: "Corex" } }; const out = formatRegistrationEntry(entry, 0); assert.ok(out.includes("1. Corey J")); }); it("uses user.best_identifier when top-level best_identifier missing", () => { const entry = { user: { best_identifier: "Deviknyte" } }; const out = formatRegistrationEntry(entry, 0); assert.ok(out.includes("1. Deviknyte")); }); it("uses user.username when display_name missing", () => { const entry = { user: { username: "u1" } }; const out = formatRegistrationEntry(entry, 0); assert.ok(out.includes("1. u1")); }); it("includes Registered line for invalid date string (Invalid Date or raw fallback)", () => { const entry = { display_name: "X", registered_at: "not-a-date", }; const out = formatRegistrationEntry(entry, 0); assert.ok(out.includes("Registered:"), "should include Registered line"); // new Date("not-a-date") does not throw; toLocaleString() yields "Invalid Date" in most envs assert.ok(out.includes("Invalid Date") || out.includes("not-a-date"), "should show Invalid Date or raw value"); }); }); describe("parseRecordToWinsLosses", () => { it("parses W-L format", () => { assert.deepStrictEqual(parseRecordToWinsLosses("3-0"), { wins: 3, losses: 0 }); assert.deepStrictEqual(parseRecordToWinsLosses("4-2"), { wins: 4, losses: 2 }); assert.deepStrictEqual(parseRecordToWinsLosses("0-3"), { wins: 0, losses: 3 }); }); it("parses W-L-T format (uses first two numbers)", () => { assert.deepStrictEqual(parseRecordToWinsLosses("3-0-1"), { wins: 3, losses: 0 }); assert.deepStrictEqual(parseRecordToWinsLosses("2-1-1"), { wins: 2, losses: 1 }); }); it("returns zeros for undefined, empty, or unparseable", () => { assert.deepStrictEqual(parseRecordToWinsLosses(undefined), { wins: 0, losses: 0 }); assert.deepStrictEqual(parseRecordToWinsLosses(""), { wins: 0, losses: 0 }); assert.deepStrictEqual(parseRecordToWinsLosses(" "), { wins: 0, losses: 0 }); assert.deepStrictEqual(parseRecordToWinsLosses("invalid"), { wins: 0, losses: 0 }); assert.deepStrictEqual(parseRecordToWinsLosses("3"), { wins: 0, losses: 0 }); }); it("handles spaces around dashes", () => { assert.deepStrictEqual(parseRecordToWinsLosses("3 - 0 - 1"), { wins: 3, losses: 0 }); }); }); describe("formatLeaderboardEntry", () => { it("formats entry with wins, losses, events, win rate, best and avg placement, 1st places", () => { const entry = { playerName: "Alice", totalWins: 10, totalLosses: 2, eventsPlayed: 3, bestPlacement: 1, firstPlaceFinishes: 1, placements: [1, 3, 2], }; const out = formatLeaderboardEntry(entry, 1); assert.ok(out.includes("1. Alice")); assert.ok(out.includes("Wins: 10")); assert.ok(out.includes("Losses: 2")); assert.ok(out.includes("Events: 3")); assert.ok(out.includes("Win Rate: 83.3%")); assert.ok(out.includes("Best: 1st")); assert.ok(out.includes("Avg: 2.0")); assert.ok(out.includes("1st places: 1")); }); it("shows em dash for win rate when no games", () => { const entry = { playerName: "Bob", totalWins: 0, totalLosses: 0, eventsPlayed: 1, bestPlacement: 5, firstPlaceFinishes: 0, placements: [5], }; const out = formatLeaderboardEntry(entry, 2); assert.ok(out.includes("Win Rate: —%")); }); it("formats 2nd and 3rd placement suffixes", () => { const second = { playerName: "Second", totalWins: 0, totalLosses: 0, eventsPlayed: 1, bestPlacement: 2, firstPlaceFinishes: 0, placements: [2], }; const third = { playerName: "Third", totalWins: 0, totalLosses: 0, eventsPlayed: 1, bestPlacement: 3, firstPlaceFinishes: 0, placements: [3], }; assert.ok(formatLeaderboardEntry(second, 1).includes("Best: 2nd")); assert.ok(formatLeaderboardEntry(third, 1).includes("Best: 3rd")); }); }); describe("formatLeaderboard", () => { it("includes period, events analyzed, and sort label", () => { const result = { players: [], eventsAnalyzed: 5, eventsIncluded: [], dateRange: { start: "2026-01-01", end: "2026-01-31" }, }; const out = formatLeaderboard(result, "TOTAL WINS"); assert.ok(out.includes("Player Leaderboard")); assert.ok(out.includes("2026-01-01 – 2026-01-31")); assert.ok(out.includes("Events analyzed: 5")); assert.ok(out.includes("TOP PLAYERS BY TOTAL WINS")); }); it("includes filter context when present", () => { const result = { players: [], eventsAnalyzed: 0, eventsIncluded: [], dateRange: { start: "2026-01-01", end: "2026-01-31" }, filters: { city: "Detroit, MI", categories: ["Set Championship"] }, }; const out = formatLeaderboard(result, "TOTAL WINS"); assert.ok(out.includes("near Detroit, MI")); assert.ok(out.includes("Set Championship")); }); it("includes store in filter context when present", () => { const result = { players: [], eventsAnalyzed: 0, eventsIncluded: [], dateRange: { start: "2026-01-01", end: "2026-01-31" }, filters: { store: "Game Haven", formats: ["Constructed"] }, }; const out = formatLeaderboard(result, "TOTAL WINS"); assert.ok(out.includes("at Game Haven")); assert.ok(out.includes("Constructed")); }); it("lists events included", () => { const result = { players: [ { playerName: "Alice", totalWins: 3, totalLosses: 0, eventsPlayed: 1, bestPlacement: 1, firstPlaceFinishes: 1, placements: [1], }, ], eventsAnalyzed: 1, eventsIncluded: [ { id: 100, name: "Championship Jan 5", startDate: "2026-01-05T18:00:00Z" }, ], dateRange: { start: "2026-01-01", end: "2026-01-31" }, }; const out = formatLeaderboard(result, "TOTAL WINS"); assert.ok(out.includes("1. Alice")); assert.ok(out.includes("Events included:")); assert.ok(out.includes("Championship Jan 5")); assert.ok(out.includes("ID: 100")); }); });