Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
import { check, getApi, getPb, wait, waitMs, users } from "./common.js";
import { benderTiers } from "./spaceshipBook.js";

export async function userOrgManagementTests(api) {
  const testUser = {
    email: "userorg.test@planetexpress.com",
    password: "testpassword123",
    name: "User Org Test User",
  };

  const orgOwner1 = {
    email: "owner1@company.com",
    password: "owner1password123",
    name: "First Company Owner",
    org: "First Test Company",
    orgId: "",
    bookId: "",
  };

  const orgOwner2 = {
    email: "owner2@company.com",
    password: "owner2password123",
    name: "Second Company Owner",
    org: "Second Test Company",
    orgId: "",
    bookId: "",
  };

  // Setup: Register users and create organizations
  await check(async () => {
    await api.register(testUser);
    await api.register(orgOwner1);
    await api.register(orgOwner2);

    const responseA = await api.createOrg(orgOwner1, orgOwner1.org);
    const responseB = await api.createOrg(orgOwner2, orgOwner2.org);

    orgOwner1.orgId = responseA.id;
    orgOwner2.orgId = responseB.id;

    const book1 = await api.createBook(
      orgOwner1,
      orgOwner1.orgId,
      "Book One Tiers",
      "tiersBook",
      null,
      null,
    );

    const book2 = await api.createBook(
      orgOwner2,
      orgOwner2.orgId,
      "Book Two Tiers",
      "tiersBook",
      null,
      null,
    );

    orgOwner1.bookId = book1.id;
    orgOwner2.bookId = book2.id;

    await api.publishChanges(orgOwner1, orgOwner1.orgId, book1.id, benderTiers);
    await api.publishChanges(orgOwner2, orgOwner2.orgId, book2.id, benderTiers);

    // User joins both organizations
    await api.joinOrg(testUser, orgOwner1.orgId);
    await api.joinOrg(testUser, orgOwner2.orgId); // activeOrg should be Second Test Company

    await api.assignRole(orgOwner1, testUser, "author");
    await api.assignRole(orgOwner2, testUser, "author");

    return true;
  }, "Setup users and organizations, user joins both organizations");

  // Test 1: User can update their activeOrg field
  await check(async () => {
    const updatedUser = await api.updateUserActiveOrg(
      testUser,
      orgOwner1.orgId,
    );

    if (updatedUser.activeOrg !== orgOwner1.orgId) {
      throw new Error(
        "Failed to update user's activeOrg to First Test Company",
      );
    }

    return updatedUser;
  }, "User can update their activeOrg to a new organization");

  await check(async () => {
    // Verify the activeOrg change persists after re-login
    await api.login(testUser);
    const pb = api.pb;

    if (pb.authStore.record.activeOrg !== orgOwner1.orgId) {
      throw new Error("activeOrg change should persist after re-login");
    }

    return pb.authStore.record;
  }, "Updated activeOrg persists after re-login");

  await check(async () => {
    // Test updating to the second organization
    const updatedUser = await api.updateUserActiveOrg(
      testUser,
      orgOwner2.orgId,
    );

    if (updatedUser.activeOrg !== orgOwner2.orgId) {
      throw new Error(
        "Failed to update user's activeOrg to Second Test Company",
      );
    }

    return updatedUser;
  }, "User can update their activeOrg to a different organization");

  // Test 2: User can list all profiles where profile.user matches their user ID
  await check(async () => {
    const userProfiles = await api.listUserProfiles(testUser);
    console.log(userProfiles);

    // User should have profiles in both organizations they joined
    if (userProfiles.length !== 2) {
      throw new Error(`Expected 2 profiles, but found ${userProfiles.length}`);
    }

    // Verify that all profiles belong to the test user
    const userId = (await api.login(testUser)).record.id;
    for (const profile of userProfiles) {
      if (profile.user !== userId) {
        throw new Error(
          `Profile ${profile.id} does not belong to the test user`,
        );
      }
    }

    return userProfiles;
  }, "User can list all their profiles across organizations");

  await check(async () => {
    const updatedUser = await api.updateUserActiveOrg(
      testUser,
      orgOwner1.orgId,
    );

    const book = await api.viewBook(testUser, orgOwner1.bookId);
    if (book.id != orgOwner1.bookId) {
      throw new Error("Should be able to see the correct book for the org");
    }

    const allBooks = await api.listBooks(testUser);
    console.log(allBooks);
  }, "User can list pricebooks in the first organization");

  await check(async () => {
    const updatedUser = await api.updateUserActiveOrg(
      testUser,
      orgOwner2.orgId,
    );
    const book = await api.viewBook(testUser, orgOwner2.bookId);
    if (book.id != orgOwner2.bookId) {
      throw new Error("Should be able to see the correct book for the org");
    }

    const allBooks = await api.listBooks(testUser);
    console.log(allBooks);
  }, "User can list pricebooks in the second organization");

  await check(async () => {
    const userProfiles = await api.listUserProfiles(testUser);
    // Extract organization IDs from the profiles
    const profileOrgIds = userProfiles.map((p) => p.org);

    // Verify profiles exist for both organizations the user joined
    if (!profileOrgIds.includes(orgOwner1.orgId)) {
      throw new Error("User should have a profile in First Test Company");
    }

    if (!profileOrgIds.includes(orgOwner2.orgId)) {
      throw new Error("User should have a profile in Second Test Company");
    }

    return userProfiles;
  }, "User profiles include correct organizations");

  await check(async () => {
    const userProfiles = await api.listUserProfiles(testUser);

    // Verify expanded data is included
    for (const profile of userProfiles) {
      if (!profile.expand || !profile.expand.user) {
        throw new Error("Profile should include expanded user data");
      }

      if (profile.expand.user.name !== testUser.name) {
        throw new Error("Expanded user data should match the test user");
      }
    }

    return userProfiles;
  }, "User profiles include properly expanded user data");

  // Test edge case: Verify other users cannot see this user's profiles unless they're in the same org
  await check(async () => {
    // Create a third user who is not in any of the test organizations
    const outsideUser = {
      email: "outside@test.com",
      password: "outsidepassword123",
      name: "Outside User",
    };

    await api.register(outsideUser);

    try {
      const outsideUserProfiles = await api.listUserProfiles(outsideUser);

      // Outside user should have no profiles since they haven't joined any organizations
      if (outsideUserProfiles.length !== 0) {
        throw new Error(
          `Outside user should have 0 profiles, but found ${outsideUserProfiles.length}`,
        );
      }

      return outsideUserProfiles;
    } catch (error) {
      // This is expected if the user has no profiles
      return [];
    }
  }, "Users who haven't joined organizations have no profiles");

  // Test security: Users cannot list profiles that don't belong to them
  await check(async () => {
    // Create a fourth user who joins only one organization
    const limitedUser = {
      email: "limited@test.com",
      password: "limitedpassword123",
      name: "Limited User",
    };

    await api.register(limitedUser);
    await api.joinOrg(limitedUser, orgOwner1.orgId); // Only joins First Test Company

    const limitedUserProfiles = await api.listUserProfiles(limitedUser);

    // Limited user should only see their own profile (1 profile)
    if (limitedUserProfiles.length !== 1) {
      throw new Error(
        `Limited user should only see 1 profile (their own), but found ${limitedUserProfiles.length}`,
      );
    }

    // Verify the profile belongs to the limited user
    const userId = (await api.login(limitedUser)).record.id;
    if (limitedUserProfiles[0].user !== userId) {
      throw new Error("Limited user should only see their own profile");
    }

    // Verify the profile is from the correct organization
    if (limitedUserProfiles[0].org !== orgOwner1.orgId) {
      throw new Error(
        "Limited user's profile should be from First Test Company",
      );
    }

    return limitedUserProfiles;
  }, "Users can only list their own profiles, not other users' profiles");

  // Test that users cannot access profiles from organizations they're not members of
  await check(async () => {
    // Create a user who joins a different organization entirely
    const separateOrgOwner = {
      email: "separate@company.com",
      password: "separatepassword123",
      name: "Separate Company Owner",
      org: "Separate Test Company",
      orgId: "",
    };

    const separateUser = {
      email: "separate.user@company.com",
      password: "separateuserpassword123",
      name: "Separate Company User",
    };

    await api.register(separateOrgOwner);
    await api.register(separateUser);

    const separateOrgResponse = await api.createOrg(
      separateOrgOwner,
      separateOrgOwner.org,
    );
    separateOrgOwner.orgId = separateOrgResponse.id;

    await api.joinOrg(separateUser, separateOrgOwner.orgId);

    // Separate user should only see their own profile from their organization
    const separateUserProfiles = await api.listUserProfiles(separateUser);

    if (separateUserProfiles.length !== 1) {
      throw new Error(
        `Separate user should only see 1 profile, but found ${separateUserProfiles.length}`,
      );
    }

    // Verify it's their own profile from their organization
    const userId = (await api.login(separateUser)).record.id;
    if (separateUserProfiles[0].user !== userId) {
      throw new Error("Separate user should only see their own profile");
    }

    if (separateUserProfiles[0].org !== separateOrgOwner.orgId) {
      throw new Error(
        "Separate user's profile should be from Separate Test Company",
      );
    }

    return separateUserProfiles;
  }, "Users cannot access profiles from organizations they don't belong to");
}