diff --git a/src/config/config_model.py b/src/config/config_model.py index d77fda4..20d668e 100644 --- a/src/config/config_model.py +++ b/src/config/config_model.py @@ -10,13 +10,9 @@ class MongoDBCollections(BaseModel): users: str = "Users" - domains: str = "Domains" - groups: str = "Groups" organizations: str = "Organizations" - group_roles: str = "GroupRoles" + organization_members: str = "OrganizationMembers" internal_counters: str = "InternalCounters" - group_members: str = "GroupMembers" - contests: str = "Contests" class DatabaseConfig(BaseModel): diff --git a/src/db/methods/__init__.py b/src/db/methods/__init__.py index 4d7740d..3c80016 100644 --- a/src/db/methods/__init__.py +++ b/src/db/methods/__init__.py @@ -1,12 +1,4 @@ -from . import ( - users, - domains, - organizations, -) +from . import methods -__all__ = [ - "users", - "domains", - "organizations", -] +__all__ = ["methods"] diff --git a/src/db/methods/collections/__init__.py b/src/db/methods/collections/__init__.py index 66f605c..3734808 100644 --- a/src/db/methods/collections/__init__.py +++ b/src/db/methods/collections/__init__.py @@ -1,12 +1,9 @@ -from .collections import users, domains, contests, group_roles, group_members, organizations, internal_counters +from .collections import users, organizations, internal_counters, organization_members __all__ = [ "users", - "domains", - "contests", - "group_roles", - "group_members", "organizations", "internal_counters", + "organization_members", ] diff --git a/src/db/methods/collections/collections.py b/src/db/methods/collections/collections.py index 81a76ac..2fbaa55 100644 --- a/src/db/methods/collections/collections.py +++ b/src/db/methods/collections/collections.py @@ -6,13 +6,9 @@ users = db.get_collection(config.database.collections.users) organizations = db.get_collection(config.database.collections.organizations) -groups = db.get_collection(config.database.collections.groups) -domains = db.get_collection(config.database.collections.domains) -contests = db.get_collection(config.database.collections.contests) -group_roles = db.get_collection(config.database.collections.group_roles) -group_members = db.get_collection(config.database.collections.group_members) +organization_members = db.get_collection(config.database.collections.organization_members) internal_counters = db.get_collection(config.database.collections.internal_counters) -users.create_index([("email", 1)], unique=True, name="email") -# domains.create_index([("target_id", 1)], unique=True, name="target_id") +users.create_index([("email", 1)], unique=True) +organization_members.create_index([("target_id", 1), ("object_id", 1)], unique=True) diff --git a/src/db/methods/domains.py b/src/db/methods/domains.py deleted file mode 100644 index 543ffd6..0000000 --- a/src/db/methods/domains.py +++ /dev/null @@ -1,18 +0,0 @@ -from pymongo.client_session import ClientSession - -from db.types import types -from .collections import domains - - -def attach_to_entity( - domain: str, target_id: int, target_type: types.EntityTargetType, session: ClientSession | None = None -) -> None: - domains.find_one_and_update( - {"_id": domain}, {"$set": {"target_id": target_id, "target_type": target_type}}, session=session, upsert=True - ) - - -def resolve_entity(domain: str, session: ClientSession | None = None) -> types.Entity | None: - if (entity := domains.find_one({"_id": domain}, session=session)) is None: - return None - return types.Entity(**entity) diff --git a/src/db/methods/methods.py b/src/db/methods/methods.py new file mode 100644 index 0000000..ac80f3b --- /dev/null +++ b/src/db/methods/methods.py @@ -0,0 +1,83 @@ +from pymongo.client_session import ClientSession +from pymongo.errors import DuplicateKeyError + +from db.types import types +from db.methods.helpers import insert_with_auto_increment_id +from .collections import users, organizations, organization_members + + +# Organizations +def get_organization(organization_id: int, s: ClientSession | None = None) -> types.Organization | None: + if (organization := organizations.find_one({"_id": organization_id}, session=s)) is None: + return None + return types.Organization(**organization) + + +def check_organization_existence(organization_id: int, s: ClientSession | None = None) -> bool: + return organizations.count_documents({"_id": organization_id}, session=s) > 0 + + +def insert_organization(organization: types.OrganizationWithoutID, s: ClientSession | None = None) -> int: + return insert_with_auto_increment_id(organizations, organization.db_dump(), session=s) + + +def insert_member_to_organization(member: types.Member, s: ClientSession | None = None) -> bool: + try: + organization_members.insert_one(member.db_dump(), session=s) + except DuplicateKeyError: + return False + return True + + +def get_organizations_by_user(user_id: int, s: ClientSession | None = None) -> list[types.Organization]: + + pipeline = [ + {"$match": {"object_id": user_id}}, + { + "$lookup": { + "from": organizations.name, + "localField": "target_id", + "foreignField": "_id", + "as": "organizations", + } + }, + {"$unwind": "$organizations"}, + {"$replaceRoot": {"newRoot": "$organizations"}}, + ] + return [types.Organization(**org) for org in organization_members.aggregate(pipeline, session=s)] + + +def is_user_in_organization(user_id: int, organization_id: int, s: ClientSession | None = None) -> bool: + return organization_members.count_documents({"object_id": user_id, "target_id": organization_id}, session=s) > 0 + + +def get_members_of_organization(organization_id: int, s: ClientSession | None = None) -> list[int]: + pipeline = [ + {"$match": {"target_id": organization_id}}, + {"$group": {"_id": None, "result": {"$push": "$object_id"}}}, + ] + return next(organization_members.aggregate(pipeline, session=s)).get("result", None) + + +# Users +def get_user(user_id: int, s: ClientSession | None = None) -> types.User | None: + if (user := users.find_one({"_id": user_id}, session=s)) is None: + return None + return types.User(**user) + + +def get_user_by_email(email: str, s: ClientSession | None = None): + if (user := users.find_one({"email": email}, session=s)) is None: + return None + return types.User(**user) + + +def insert_user(user: types.UserWithoutID, s: ClientSession | None = None) -> int | None: + try: + return insert_with_auto_increment_id(users, user.db_dump(), session=s) + except DuplicateKeyError: + return None + + +def check_user_existence(user_id: int, s: ClientSession | None = None) -> bool: + return users.count_documents({"_id": user_id}, session=s) > 0 diff --git a/src/db/methods/organizations.py b/src/db/methods/organizations.py deleted file mode 100644 index 3282a0d..0000000 --- a/src/db/methods/organizations.py +++ /dev/null @@ -1,48 +0,0 @@ -from pymongo.client_session import ClientSession - -from db.types import types -from db.methods.helpers import insert_with_auto_increment_id -from utils.response import ErrorCodes, ErrorResponse -from .collections import organizations - - -def get(organization_id: int, session: ClientSession | None = None) -> types.Organization | None: - if (organization := organizations.find_one({"_id": organization_id}, session=session)) is None: - return None - return types.Organization(**organization) - - -def check_existence(organization_id: int, session: ClientSession | None = None) -> bool: - return organizations.count_documents({"_id": organization_id}, session=session) > 0 - - -def insert_organization(organization: types.OrganizationWithoutID, session: ClientSession | None = None) -> int: - return insert_with_auto_increment_id(organizations, organization.db_dump(), session=session) - - -def add_member(organization_id: int, member: types.Member, session: ClientSession | None = None) -> None: - if is_user_in_organization(member.id, organization_id): - raise ErrorResponse(code=ErrorCodes.USER_ALREADY_MEMBER) - - organizations.update_one( - {"_id": organization_id}, - {"$push": {"members": member.model_dump()}}, - session=session, - ) - - -def get_organizations_by_user(user_id: int, session: ClientSession | None = None) -> list[types.Organization]: - return [ - types.Organization(**org) - for org in organizations.aggregate([{"$match": {"members": {"$elemMatch": {"id": user_id}}}}], session=session) - ] - - -def is_user_in_organization(user_id: int, organization_id: int, session: ClientSession | None = None) -> bool: - return ( - organizations.count_documents( - {"_id": organization_id, "members": {"$elemMatch": {"id": user_id}}}, - session=session, - ) - > 0 - ) diff --git a/src/db/methods/users.py b/src/db/methods/users.py deleted file mode 100644 index 4073199..0000000 --- a/src/db/methods/users.py +++ /dev/null @@ -1,33 +0,0 @@ -from pymongo.errors import DuplicateKeyError -from pymongo.client_session import ClientSession - -from db.types import types -from .collections import users -from .helpers import insert_with_auto_increment_id - - -def get(user_id: int, session: ClientSession | None = None) -> types.User | None: - if (user := users.find_one({"_id": user_id}, session=session)) is None: - return None - return types.User(**user) - - -def get_by_email(email: str, session: ClientSession | None = None): - if (user := users.find_one({"email": email}, session=session)) is None: - return None - return types.User(**user) - - -def insert_user(user: types.UserWithoutID, session: ClientSession | None = None) -> int | None: - """ - Returns: - Inserted user id or None, if error occurred - """ - try: - return insert_with_auto_increment_id(users, user.db_dump(), session=session) - except DuplicateKeyError: - return None - - -def check_existence(user_id: int, session: ClientSession | None = None) -> bool: - return users.count_documents({"_id": user_id}, session=session) > 0 diff --git a/src/db/types/requests.py b/src/db/types/requests.py index 48effc2..5e1d331 100644 --- a/src/db/types/requests.py +++ b/src/db/types/requests.py @@ -11,20 +11,22 @@ class RQ: class auth: class signin(BaseModel): id: int | None = None - domain: str | None = None email: str | None = None password: str @model_validator(mode="after") def check_only_one_field(self): error_message = "You must provide exactly one of the fields: email, domain, user_id" - assert sum(x is not None for x in (self.id, self.domain, self.email)) == 1, error_message + assert sum(x is not None for x in (self.id, self.email)) == 1, error_message return self class organizations: class get(BaseModel): id: int + class get_members(BaseModel): + id: int + class users: class get_organizations(BaseModel): id: int diff --git a/src/db/types/responses.py b/src/db/types/responses.py index e327eda..e957b16 100644 --- a/src/db/types/responses.py +++ b/src/db/types/responses.py @@ -27,6 +27,9 @@ class get_organizations(BaseModel): class organizations: class get(types.Organization): ... + class get_members(BaseModel): + members: list[int] + class test: class signup(BaseModel): access_token: str diff --git a/src/db/types/types.py b/src/db/types/types.py index bacc2fe..c29d7f7 100644 --- a/src/db/types/types.py +++ b/src/db/types/types.py @@ -1,23 +1,10 @@ -from typing import Literal - from utils.schemas import BaseModel +# Misc class Member(BaseModel): - id: int - roles: list[str] = [] - custom_permissions: int = 0 - - -class Role(BaseModel): - id: str - name: str - permissions: int - - -class _HasMembersWithRoles(BaseModel): - members: list[Member] - roles: list[Role] = [] + object_id: int + target_id: int class JWTPair(BaseModel): @@ -25,20 +12,10 @@ class JWTPair(BaseModel): refresh_token: str -EntityTargetType = Literal["user", "group", "contest"] - - -class Entity(BaseModel): - id: str - target_type: EntityTargetType - target_id: int - - +# Users class _UserBase(BaseModel): - domain: str | None = None first_name: str last_name: str | None = None - groups: list[int] = [] hashed_password: str email: str @@ -50,7 +27,8 @@ class User(_UserBase): id: int -class _OrganizationBase(_HasMembersWithRoles): +# Organizations +class _OrganizationBase(BaseModel): name: str diff --git a/src/routers/auth.py b/src/routers/auth.py index 486c85b..26921b5 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -4,7 +4,7 @@ import utils.auth from utils.auth import get_current_user_by_refresh_token from utils.response import SuccessfulResponse, ErrorCodes, ErrorResponse -from db import methods +from db.methods import methods from db.types import types, RS, RQ @@ -15,13 +15,9 @@ async def signin(request: RQ.auth.signin): user: types.User | None = None if request.id: - user = methods.users.get(request.id) - elif request.domain: - entity = methods.domains.resolve_entity(request.domain) - if entity and entity.target_type == "user": - user = methods.users.get(entity.target_id) + user = methods.get_user(request.id) elif request.email: - user = methods.users.get_by_email(request.email) + user = methods.get_user_by_email(request.email) if user is None: raise ErrorResponse( diff --git a/src/routers/organizations.py b/src/routers/organizations.py index 3b17e59..bc9c2aa 100644 --- a/src/routers/organizations.py +++ b/src/routers/organizations.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends -from db import methods +from db.methods import methods from db.types import types, RQ, RS from utils.auth.auth import get_current_user from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse @@ -11,7 +11,14 @@ @router.get("/get", response_model=SuccessfulResponse[RS.organizations.get]) async def get(request: RQ.organizations.get = Depends(), _current_user: types.User = Depends(get_current_user)): - if (organization := methods.organizations.get(request.id)) is None: + if (organization := methods.get_organization(request.id)) is None: raise ErrorResponse(code=ErrorCodes.NOT_FOUND) return organization + + +@router.get("/get_members", response_model=SuccessfulResponse[RS.organizations.get_members]) +async def get_members( + request: RQ.organizations.get_members = Depends(), _current_user: types.User = Depends(get_current_user) +): + return RS.organizations.get_members(members=methods.get_members_of_organization(request.id)) diff --git a/src/routers/test.py b/src/routers/test.py index e86ce41..320f9de 100644 --- a/src/routers/test.py +++ b/src/routers/test.py @@ -6,7 +6,7 @@ import utils.auth from config import config from utils.response import ErrorCodes, ErrorResponse, SuccessfulResponse -from db import methods +from db.methods import methods from db.client import client from db.types import types, RS, RQ @@ -26,8 +26,7 @@ async def cleanup(): @router.post("/signup", response_model=SuccessfulResponse[RS.test.signup]) async def signup(request: RQ.test.signup): hashed_password = utils.auth.hash_password(request.password) - - inserted_user_id = methods.users.insert_user( + inserted_user_id = methods.insert_user( types.UserWithoutID( email=request.email, hashed_password=hashed_password, @@ -51,11 +50,9 @@ async def create_organization( request: RQ.test.organizations.create, current_user: types.User = Depends(utils.auth.get_current_user), ): - inserted_id = methods.organizations.insert_organization( - types.OrganizationWithoutID(name=request.name, members=[types.Member(id=current_user.id)]) - ) - - return RS.test.organizations.create(members=[types.Member(id=current_user.id)], name=request.name, id=inserted_id) + inserted_id = methods.insert_organization(types.OrganizationWithoutID(name=request.name)) + methods.insert_member_to_organization(types.Member(object_id=current_user.id, target_id=inserted_id)) + return types.Organization(id=inserted_id, name=request.name) @router.post("/organizations/invite", response_model=SuccessfulResponse[None]) @@ -63,24 +60,18 @@ async def invite_user_to_organization( request: RQ.test.organizations.invite, current_user: types.User = Depends(utils.auth.get_current_user), ): - with client.start_session() as session: - with session.start_transaction(): - if not methods.organizations.check_existence(request.organization_id, session): - raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="Organization not found") - - if not methods.users.check_existence(request.user_id, session): - raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="User not found") - - if not methods.organizations.is_user_in_organization(current_user.id, request.organization_id, session): - raise ErrorResponse( - code=ErrorCodes.ACCESS_DENIED, - message="You are not a member of this organization", - ) - - if methods.organizations.is_user_in_organization(request.user_id, request.organization_id, session): - raise ErrorResponse( - code=ErrorCodes.USER_ALREADY_MEMBER, - message="User already in organization", - ) - - methods.organizations.add_member(request.organization_id, types.Member(id=request.user_id), session) + if not methods.check_organization_existence(request.organization_id): + raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="Organization not found") + + if not methods.check_user_existence(request.user_id): + raise ErrorResponse(code=ErrorCodes.NOT_FOUND, message="User not found") + + if not methods.is_user_in_organization(current_user.id, request.organization_id): + raise ErrorResponse(code=ErrorCodes.ACCESS_DENIED, message="You are not a member of this organization") + + is_member_inserted = methods.insert_member_to_organization( + types.Member(object_id=request.user_id, target_id=request.organization_id) + ) + + if not is_member_inserted: + raise ErrorResponse(code=ErrorCodes.USER_ALREADY_MEMBER, message="User already in organization") diff --git a/src/routers/users.py b/src/routers/users.py index 348d42b..f039c7d 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -2,7 +2,7 @@ from utils.response import SuccessfulResponse from utils.auth import get_current_user -from db import methods +from db.methods import methods from db.types import types, RS, RQ @@ -18,4 +18,4 @@ async def current(current_user: types.User = Depends(get_current_user)): async def get_organizations( request: RQ.users.get_organizations = Depends(), _current_user: types.User = Depends(get_current_user) ): - return RS.users.get_organizations(organizations=methods.organizations.get_organizations_by_user(request.id)) + return RS.users.get_organizations(organizations=methods.get_organizations_by_user(request.id)) diff --git a/src/utils/auth/auth.py b/src/utils/auth/auth.py index 0b3cca9..1b22d1c 100644 --- a/src/utils/auth/auth.py +++ b/src/utils/auth/auth.py @@ -5,7 +5,7 @@ from fastapi import Header from fastapi import status as http_status -from db import methods +from db.methods import methods from db.types import types from utils import response from config import config @@ -121,7 +121,7 @@ def get_current_user(authorization: str = Header()) -> types.User: subject = decode_jwt(token, config.auth.jwt_access_secret_key.get_secret_value())["subject"] - if subject.isnumeric() and (user := methods.users.get(int(subject))) is not None: + if subject.isnumeric() and (user := methods.get_user(int(subject))) is not None: return user raise response.ErrorResponse( @@ -136,7 +136,7 @@ def get_current_user_by_refresh_token(authorization: str = Header()) -> types.Us subject = decode_jwt(token, config.auth.jwt_refresh_secret_key.get_secret_value())["subject"] - if subject.isnumeric() and (user := methods.users.get(int(subject))) is not None: + if subject.isnumeric() and (user := methods.get_user(int(subject))) is not None: return user raise response.ErrorResponse( diff --git a/tests/src/helpers/api.ts b/tests/src/helpers/api.ts index eaeef31..7dc1aed 100644 --- a/tests/src/helpers/api.ts +++ b/tests/src/helpers/api.ts @@ -11,12 +11,15 @@ const api = { refresh: (bearerToken: string) => pactum.spec().get("/api/auth/refresh") .withBearerToken(bearerToken), signin: (request: RQ.auth.signin) => pactum.spec().post("/api/auth/signin") - .withBody(makeRequest(request)) + .withBody(makeRequest(request)), }, organizations: { get: (request: RQ.organizations.get, bearerToken: string) => pactum.spec().get("/api/organizations/get") .withBearerToken(bearerToken) - .withQueryParams(makeRequest(request)) + .withQueryParams(makeRequest(request)), + get_members: (request: RQ.organizations.get_members, bearerToken: string) => pactum.spec().get("/api/organizations/get_members") + .withBearerToken(bearerToken) + .withQueryParams(makeRequest(request)), }, test: { cleanup: () => pactum.spec().post("/api/test/cleanup"), @@ -28,7 +31,7 @@ const api = { .withBody(makeRequest(request)), invite: (request: RQ.test.organizations.invite, bearerToken: string) => pactum.spec().post("/api/test/organizations/invite") .withBearerToken(bearerToken) - .withBody(makeRequest(request)) + .withBody(makeRequest(request)), } }, users: { @@ -36,7 +39,7 @@ const api = { .withBearerToken(bearerToken), get_organizations: (request: RQ.users.get_organizations, bearerToken: string) => pactum.spec().get("/api/users/get_organizations") .withBearerToken(bearerToken) - .withQueryParams(makeRequest(request)) + .withQueryParams(makeRequest(request)), } }; diff --git a/tests/src/helpers/requests.ts b/tests/src/helpers/requests.ts index c2a8ca5..50e4aa2 100644 --- a/tests/src/helpers/requests.ts +++ b/tests/src/helpers/requests.ts @@ -11,6 +11,10 @@ namespace RQ { export interface get { id: number; } + + export interface get_members { + id: number; + } } export namespace users { export interface get_organizations { diff --git a/tests/src/helpers/responses.ts b/tests/src/helpers/responses.ts index 3d13ad7..2e5d087 100644 --- a/tests/src/helpers/responses.ts +++ b/tests/src/helpers/responses.ts @@ -23,6 +23,9 @@ namespace RS { } export namespace organizations { export interface get extends Organization {}; + export interface get_members { + members: number[]; + }; } export namespace test { export interface signup { diff --git a/tests/src/helpers/types.ts b/tests/src/helpers/types.ts index 7321a80..6c2d0c9 100644 --- a/tests/src/helpers/types.ts +++ b/tests/src/helpers/types.ts @@ -3,20 +3,7 @@ export interface JWTPair { refreshToken: string; } -interface HasMembersWithRoles { - members: { - id: number; - customPermissions: number; - roles: string[]; - }[]; - roles: { - id: number; - name: string; - permissions: number; - }[]; -} - -export interface Organization extends HasMembersWithRoles { +export interface Organization { id: number; name: string; } diff --git a/tests/src/organizations.test.ts b/tests/src/organizations.test.ts index 9346aa4..c0f6a3e 100644 --- a/tests/src/organizations.test.ts +++ b/tests/src/organizations.test.ts @@ -18,18 +18,15 @@ describe("Organizations", () => { }); test("Create and get organization", async () => { - const organizationName = "test_org"; - const organization: RS.test.organizations.create = await api.test.organizations.create({name: organizationName}, firstUser.getAccessToken()) + const organization: RS.test.organizations.create = await api.test.organizations.create({name: "test_org"}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); // Ensure that organization have only creator as a member - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) - .expectJsonLike({ok: true}) - .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, name: organizationName, members: [{id: firstUser.id}] - }); + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) + .expectJsonLike({"ok": true}) + .returns(makeResponse) + expect(members.members).toContain(firstUser.id); }); test("Invite user to organization", async () => { @@ -41,12 +38,11 @@ describe("Organizations", () => { .expectJsonLike({ok: true}); // Ensure that the organization members have been updated - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, members: [{id: firstUser.id}, {id: secondUser.id}] - }); + expect(members.members).toContain(firstUser.id); + expect(members.members).toContain(secondUser.id); }); test("Invite user to organization from unauthorized user", async () => { @@ -59,12 +55,10 @@ describe("Organizations", () => { .expectJsonLike({ok: false, error: {code: ErrorCodes.ACCESS_DENIED}}); // Ensure that the organization members remain unchanged - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, members: [{id: firstUser.id}] - }); + expect(members).toMatchObject({members: [firstUser.id]}) }); test("Invite user already in organization", async () => { @@ -81,12 +75,10 @@ describe("Organizations", () => { .expectJsonLike({ok: false, error: {code: ErrorCodes.USER_ALREADY_MEMBER}}); // Ensure that the organization members remain unchanged - const receivedOrganization: RS.organizations.get = await api.organizations.get({id: organization.id}, firstUser.getAccessToken()) + const members: RS.organizations.get_members = await api.organizations.get_members({id: organization.id}, firstUser.getAccessToken()) .expectJsonLike({ok: true}) .returns(makeResponse); - expect(receivedOrganization).toMatchObject({ - id: organization.id, members: [{id: firstUser.id}, {id: secondUser.id}] - }); + expect(members).toMatchObject({members: [firstUser.id, secondUser.id]}); }); test("Get user organizations", async () => { // Create two different organizations and ensure that after each creation method returns new organization