Ctrl K
ComunidadGuías…

server/commands/teamUpdater.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { MockInstance } from "vitest";
import { vi } from "vitest";
import type { CustomTheme } from "@shared/types";
import { randomString } from "@shared/random";
import { CommentingAccess, Plan, TeamPreference } from "@shared/types";
import { Team } from "@server/models";
import { buildAdmin, buildTeam, buildUser } from "@server/test/factories";
import { setCloudHosted, withAPIContext } from "@server/test/support";
import teamUpdater from "./teamUpdater";

describe("teamUpdater", () => {
  describe("default share expiry", () => {
    let planSpy: MockInstance<() => Plan>;

    beforeEach(() => {
      planSpy = vi.spyOn(Team.prototype, "plan", "get");
    });

    afterEach(() => {
      planSpy.mockRestore();
    });

    const update = async (days: number) => {
      const team = await buildTeam();
      const user = await buildAdmin({ teamId: team.id });
      return {
        team,
        user,
        run: () =>
          withAPIContext(user, (ctx) =>
            teamUpdater(ctx, {
              params: {
                preferences: { [TeamPreference.DefaultShareExpiryDays]: days },
              },
              user,
              team,
            })
          ),
      };
    };

    it("should allow setting with the advanced security entitlement", async () => {
      planSpy.mockReturnValue(Plan.Business);
      const { run } = await update(30);

      const updated = await run();
      await updated.reload();
      expect(
        updated.getPreference(TeamPreference.DefaultShareExpiryDays)
      ).toEqual(30);
    });

    it("should require the advanced security entitlement to set", async () => {
      planSpy.mockReturnValue(Plan.Community);
      const { run } = await update(30);

      await expect(run()).rejects.toThrow();
    });

    it("should allow removing without the entitlement", async () => {
      planSpy.mockReturnValue(Plan.Community);
      const { team, run } = await update(0);
      team.setPreference(TeamPreference.DefaultShareExpiryDays, 30);
      await team.save();

      const updated = await run();
      await updated.reload();
      expect(
        updated.getPreference(TeamPreference.DefaultShareExpiryDays)
      ).toEqual(0);
    });
  });

  describe("preferences", () => {
    it("should update preference when value changes", async () => {
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      const newValue = CommentingAccess.None;

      const updatedTeam = await withAPIContext(user, (ctx) =>
        teamUpdater(ctx, {
          params: {
            preferences: {
              [TeamPreference.Commenting]: newValue,
            },
          },
          user,
          team,
        })
      );

      expect(updatedTeam.getPreference(TeamPreference.Commenting)).toEqual(
        newValue
      );
    });

    it("should not update preference when value is the same (isEqual check)", async () => {
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      // Set a known preference value
      team.setPreference(TeamPreference.ViewersCanExport, true);
      await team.save();

      // Try to set the same value again
      const updatedTeam = await withAPIContext(user, (ctx) =>
        teamUpdater(ctx, {
          params: {
            preferences: {
              [TeamPreference.ViewersCanExport]: true,
            },
          },
          user,
          team,
        })
      );

      // Verify the value is still correct
      expect(
        updatedTeam.getPreference(TeamPreference.ViewersCanExport)
      ).toEqual(true);
    });

    it("should handle complex preference values with deep equality", async () => {
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      // Set initial theme
      team.setPreference(TeamPreference.CustomTheme, {
        accent: "#FF0000",
        accentText: "#00FF00",
      });
      await team.save();

      // Pass an object with the same values (but different reference)
      const updatedTeam = await withAPIContext(user, (ctx) =>
        teamUpdater(ctx, {
          params: {
            preferences: {
              [TeamPreference.CustomTheme]: {
                accent: "#FF0000",
                accentText: "#00FF00",
              },
            },
          },
          user,
          team,
        })
      );

      // The theme should still be set correctly with isEqual comparison
      const theme = updatedTeam.getPreference(
        TeamPreference.CustomTheme
      ) as Partial<CustomTheme>;
      expect(theme?.accent).toEqual("#FF0000");
      expect(theme?.accentText).toEqual("#00FF00");
    });

    it("should update only preferences that have different values", async () => {
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      // Set initial values
      team.setPreference(TeamPreference.Commenting, CommentingAccess.Members);
      team.setPreference(TeamPreference.ViewersCanExport, true);
      await team.save();

      const updatedTeam = await withAPIContext(user, (ctx) =>
        teamUpdater(ctx, {
          params: {
            preferences: {
              // Same - should skip due to isEqual
              [TeamPreference.Commenting]: CommentingAccess.Members,
              [TeamPreference.ViewersCanExport]: false, // Different - should update
            },
          },
          user,
          team,
        })
      );

      expect(updatedTeam.getPreference(TeamPreference.Commenting)).toEqual(
        CommentingAccess.Members
      );
      expect(
        updatedTeam.getPreference(TeamPreference.ViewersCanExport)
      ).toEqual(false);
    });
  });

  describe("basic attributes", () => {
    it("should update team name", async () => {
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      const updatedTeam = await withAPIContext(user, (ctx) =>
        teamUpdater(ctx, {
          params: { name: "Updated Team Name" },
          user,
          team,
        })
      );

      expect(updatedTeam.name).toEqual("Updated Team Name");
    });
  });

  describe("subdomain", () => {
    beforeEach(setCloudHosted);

    it("should update subdomain when available", async () => {
      const subdomain = `available-${randomString({ length: 10, charset: "alphabetic", capitalization: "lowercase" })}`;
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      const updatedTeam = await withAPIContext(user, (ctx) =>
        teamUpdater(ctx, {
          params: { subdomain },
          user,
          team,
        })
      );

      expect(updatedTeam.subdomain).toEqual(subdomain);
    });

    it("should throw a validation error when subdomain is taken", async () => {
      const subdomain = `taken-${randomString({ length: 10, charset: "alphabetic", capitalization: "lowercase" })}`;
      await buildTeam({ subdomain });
      const team = await buildTeam();
      const user = await buildUser({ teamId: team.id });

      await expect(
        withAPIContext(user, (ctx) =>
          teamUpdater(ctx, {
            params: { subdomain },
            user,
            team,
          })
        )
      ).rejects.toThrow("Subdomain is already in use");
    });
  });
});