jest.mock('./firebase/config');

import functionsTest from 'firebase-functions-test';
import { createGame } from './createGame';
import { db } from './firebase/config';

const testEnv = functionsTest();

const mockCollection = db.collection as jest.Mock;
const mockAdd = jest.fn();

describe('createGame', () => {
    let wrapped: any;

    beforeAll(() => {
        wrapped = testEnv.wrap(createGame);
    });

    beforeEach(() => {
        jest.resetAllMocks();

        const mockWhere = jest.fn().mockReturnThis();
        const mockGet = jest.fn().mockResolvedValue({ empty: true });

        mockCollection.mockImplementation(() => ({
            where: mockWhere,
            get: mockGet,
            add: mockAdd,
        }));

        mockAdd.mockResolvedValue({ id: 'game123' });
    });

    afterAll(() => {
        testEnv.cleanup();
    });

    it('should successfully create a new game with valid inputs', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        const result = await wrapped(data, context);

        expect(result).toHaveProperty('gameId', 'game123');
        expect(result).toHaveProperty('gameCode');

        expect(mockCollection).toHaveBeenCalledWith('games');
        expect(mockAdd).toHaveBeenCalledWith(expect.objectContaining({
            createdBy: 'TestPlayer',
            roundHost: 'TestPlayer',
            teams: expect.arrayContaining([
                expect.objectContaining({
                    players: expect.arrayContaining([{ name: 'TestPlayer' }])
                })
            ])
        }));
    });

    it('should throw an unauthenticated error if user is not authenticated', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {}; // No auth

        await expect(wrapped(data, context)).rejects.toThrow(
            'The function must be called while authenticated.'
        );
    });

    it('should throw an invalid-argument error for missing name', async () => {
        const data = {
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Name is required and must be between 3 and 20 characters.'
        );
    });

    it('should throw an invalid-argument error for missing numberOfTeams', async () => {
        const data = {
            name: 'TestPlayer',
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of teams is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid playersPerTeam', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: -1,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of players per team is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid numberOfRounds', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 0,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of rounds is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid jokerUsage', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: 'yes', // Invalid boolean
        };
        const context = { auth: { uid: 'test-uid' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Joker usage must be a boolean.'
        );
    });

    it('should throw an internal error if Firestore add fails', async () => {
        mockAdd.mockRejectedValueOnce(new Error('Firestore add error'));

        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Unable to create game at this time.'
        );

        expect(mockAdd).toHaveBeenCalled();
    });

    it('should retry generating a unique game code on collision', async () => {
        const mockGet = jest.fn()
            .mockResolvedValueOnce({ empty: false }) // Collision
            .mockResolvedValueOnce({ empty: true }); // No collision

        mockCollection.mockImplementation(() => ({
            where: jest.fn().mockReturnThis(),
            get: mockGet,
            add: mockAdd,
        }));

        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = { auth: { uid: 'test-uid' } };

        const result = await wrapped(data, context);

        expect(result).toHaveProperty('gameId', 'game123');
        expect(result).toHaveProperty('gameCode');

        expect(mockGet).toHaveBeenCalledTimes(2);
    });
});

----

import * as functions from 'firebase-functions';
import { db as firestore } from './firebase/config';
import { v4 as uuidv4 } from 'uuid';
import { Game, Team, Player } from './types';
import { FieldValue } from 'firebase-admin/firestore';

/**
 * Firebase Cloud Function to create a new game session.
 * Initializes a game document in Firestore with specified parameters,
 * generates a unique game code, assigns the creator to a team,
 * and sets them as the initial Round Host.
 */
export const createGame = functions.https.onCall(async (data, context) => {
  // Ensure the user is authenticated (using Anonymous Authentication)
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'The function must be called while authenticated.'
    );
  }

  const { name, numberOfTeams, playersPerTeam, numberOfRounds, jokerUsage } = data;

  // Validate input parameters
  if (
    !name ||
    typeof name !== 'string' ||
    name.trim().length < 3 ||
    name.trim().length > 20
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Name is required and must be between 3 and 20 characters.'
    );
  }

  if (
    !numberOfTeams ||
    typeof numberOfTeams !== 'number' ||
    numberOfTeams <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of teams is required and must be a positive integer.'
    );
  }

  if (
    !playersPerTeam ||
    typeof playersPerTeam !== 'number' ||
    playersPerTeam <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of players per team is required and must be a positive integer.'
    );
  }

  if (
    !numberOfRounds ||
    typeof numberOfRounds !== 'number' ||
    numberOfRounds <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of rounds is required and must be a positive integer.'
    );
  }

  if (typeof jokerUsage !== 'boolean') {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Joker usage must be a boolean.'
    );
  }

  /**
   * Generates a unique game code.
   * Uses a 6-character UUID substring and ensures uniqueness by checking Firestore.
   */
  const generateUniqueGameCode = async (): Promise<string> => {
    const code = uuidv4().slice(0, 6).toUpperCase(); // e.g., 'A1B2C3'
    const existingGame = await firestore
      .collection('games')
      .where('gameCode', '==', code)
      .get();
    if (!existingGame.empty) {
      // Collision detected, retry generating a code
      return generateUniqueGameCode();
    }
    return code;
  };

  try {
    const gameCode = await generateUniqueGameCode();

    const teams: Team[] = Array.from({ length: numberOfTeams }, (_, i) => ({
      teamName: `Team ${i + 1}`,
      players: [],
    }));

    const creatorPlayer: Player = {
      name: name.trim(),
    };

    // Add creator to the first team
    teams[0].players.push(creatorPlayer);

    const newGame: Game = {
      gameCode,
      numberOfTeams,
      playersPerTeam,
      numberOfRounds,
      jokerUsage,
      createdBy: name.trim(),
      roundHost: name.trim(),
      createdAt: FieldValue.serverTimestamp(),
      teams,
      currentRound: 0,
    };

    const gameRef = await firestore.collection('games').add(newGame);

    return { gameId: gameRef.id, gameCode };
  } catch (error) {
    console.error('Error creating game:', error);
    throw new functions.https.HttpsError(
      'internal',
      'Unable to create game at this time.'
    );
  }
});

-----

fix the tests based on the file