diff --git a/firestore.rules b/firestore.rules index 44c31ca..6734209 100644 --- a/firestore.rules +++ b/firestore.rules @@ -94,7 +94,8 @@ service cloud.firestore { return [requiredFields, allFields]; } - allow read, delete: if isSignedIn() && (isSignedInUser() || getGroupRole(groupId) == "owner" || isAdmin()); // is current user's data or is owner of group or is admin + allow read: if isSignedIn() && (isSignedInUser() || getGroupRole(groupId) == "owner" || isAdmin()); // is current user's data or is owner of group or is admin + allow delete: if isSignedIn() && ((isSignedInUser() && getGroupRole(groupId) != "owner") || (!isSignedInUser() && getGroupRole(groupId) == "owner") || isAdmin()) allow create: if isSignedIn() && isSignedInUser() && (getRequestField("role", "") == "member" || (isAdmin() && verifyGroupFieldTypes())) && verifyCreateFields(getPossibleGroupFields()); allow update: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()) && diff --git a/src/GroupPage.js b/src/GroupPage.js index bd854c8..2c57362 100644 --- a/src/GroupPage.js +++ b/src/GroupPage.js @@ -19,608 +19,659 @@ import "./css/GroupPage.css"; import "./css/ConfirmationDialog.css"; import "./css/OptionsListOverlay.css"; -export default withRouter(class GroupPage extends Component { - constructor(props) { - super(props); - this.state = { - user: props.user, - db: props.db, - functions: { - removeSetFromGroup: props.functions.httpsCallable("removeSetFromGroup"), - getGroupMembers: props.functions.httpsCallable("getGroupMembers"), - }, - navbarItems: [ - { - type: "link", - link: "/", - icon: , - hideTextMobile: true, - } - ], - role: null, - groupName: "", - sets: {}, - memberCount: null, - joinCode: "", - editGroupName: false, - loading: false, - groupUsers: { - owners: [], - contributors: [], - members: [], - }, - editingUser: null, - showDeleteGroup: false, - deleteGroupLoading: false, - }; +export default withRouter( + class GroupPage extends Component { + constructor(props) { + super(props); + this.state = { + user: props.user, + db: props.db, + functions: { + removeSetFromGroup: props.functions.httpsCallable("removeSetFromGroup"), + getGroupMembers: props.functions.httpsCallable("getGroupMembers"), + }, + navbarItems: [ + { + type: "link", + link: "/", + icon: , + hideTextMobile: true, + }, + ], + role: null, + groupName: "", + sets: {}, + memberCount: null, + joinCode: "", + editGroupName: false, + loading: false, + groupUsers: { + owners: [], + contributors: [], + members: [], + }, + editingUser: null, + showDeleteGroup: false, + deleteGroupLoading: false, + showLeaveGroup: false, + leaveGroupLoading: false, + }; - let isMounted = true; - Object.defineProperty(this, "isMounted", { - get: () => isMounted, - set: (value) => isMounted = value, - }); - } + let isMounted = true; + Object.defineProperty(this, "isMounted", { + get: () => isMounted, + set: (value) => (isMounted = value), + }); + } - setState = (state, callback = null) => { - if (this.isMounted) super.setState(state, callback); - } + setState = (state, callback = null) => { + if (this.isMounted) super.setState(state, callback); + }; - async componentDidMount() { - let promises = []; - let newState = { - sets: {}, - }; + async componentDidMount() { + let promises = []; + let newState = { + sets: {}, + }; - promises.push( - this.state.db - .collection("users") - .doc(this.state.user.uid) - .collection("groups") - .doc(this.props.match.params.groupId) - .get() - .then((userGroupDoc) => { - if (userGroupDoc.data().role === "owner") { - return this.state.functions - .getGroupMembers({ groupId: this.props.match.params.groupId }) - .then((response) => { - newState.groupUsers = response.data; - return userGroupDoc.data(); - }) - .catch((error) => { - newState.groupUsers = { - owners: [ - { - displayName: this.state.user.displayName, - uid: this.state.user.uid, - } - ], - contributors: [], - members: [], - }; - return userGroupDoc.data(); - }); - } - return userGroupDoc.data(); - }) - .catch((error) => { - console.log(`Can't access user group: ${error}`); - return { - role: "none", - }; - }) - ); + promises.push( + this.state.db + .collection("users") + .doc(this.state.user.uid) + .collection("groups") + .doc(this.props.match.params.groupId) + .get() + .then((userGroupDoc) => { + if (userGroupDoc.data().role === "owner") { + return this.state.functions + .getGroupMembers({ groupId: this.props.match.params.groupId }) + .then((response) => { + newState.groupUsers = response.data; + return userGroupDoc.data(); + }) + .catch((error) => { + newState.groupUsers = { + owners: [ + { + displayName: this.state.user.displayName, + uid: this.state.user.uid, + }, + ], + contributors: [], + members: [], + }; + return userGroupDoc.data(); + }); + } + return userGroupDoc.data(); + }) + .catch((error) => { + console.log(`Can't access user group: ${error}`); + return { + role: "none", + }; + }) + ); - promises.push( - this.state.db - .collection("groups") - .doc(this.props.match.params.groupId) - .get() - .then(async (groupDoc) => { - await Promise.all(groupDoc.data().sets.map((setId) => { - return this.state.db.collection("sets") - .doc(setId) - .get() - .then((doc) => { - newState.sets[setId] = { - displayName: doc.data().title, - loading: false, - }; - }); - })); + promises.push( + this.state.db + .collection("groups") + .doc(this.props.match.params.groupId) + .get() + .then(async (groupDoc) => { + await Promise.all( + groupDoc.data().sets.map((setId) => { + return this.state.db + .collection("sets") + .doc(setId) + .get() + .then((doc) => { + newState.sets[setId] = { + displayName: doc.data().title, + loading: false, + }; + }); + }) + ); - return groupDoc.data(); - }).catch((error) => { - console.log(`Can't access group: ${error}`); - return { - display_name: "", - users: {}, - join_code: "", - }; - }) - ); + return groupDoc.data(); + }) + .catch((error) => { + console.log(`Can't access group: ${error}`); + return { + display_name: "", + users: {}, + join_code: "", + }; + }) + ); - const completedPromises = await Promise.all(promises); + const completedPromises = await Promise.all(promises); - document.title = `${completedPromises[1].display_name} | Parandum`; + document.title = `${completedPromises[1].display_name} | Parandum`; - newState.role = completedPromises[0].role; - newState.groupName = completedPromises[1].display_name; - newState.originalGroupName = completedPromises[1].display_name; - newState.memberCount = Object.keys(completedPromises[1].users).length + (Object.keys(completedPromises[1].users).includes(this.state.user.uid) ? 0 : 1); - newState.joinCode = completedPromises[0].role === "owner" ? completedPromises[1].join_code : ""; + newState.role = completedPromises[0].role; + newState.groupName = completedPromises[1].display_name; + newState.originalGroupName = completedPromises[1].display_name; + newState.memberCount = + Object.keys(completedPromises[1].users).length + + (Object.keys(completedPromises[1].users).includes(this.state.user.uid) ? 0 : 1); + newState.joinCode = completedPromises[0].role === "owner" ? completedPromises[1].join_code : ""; - this.setState(newState); - this.props.page.load(); + this.setState(newState); + this.props.page.load(); - this.props.logEvent("select_content", { - content_type: "group", - item_id: this.props.match.params.groupId, - }); - } + this.props.logEvent("select_content", { + content_type: "group", + item_id: this.props.match.params.groupId, + }); + } - componentWillUnmount() { - this.isMounted = false; - this.props.page.unload(); - } + componentWillUnmount() { + this.isMounted = false; + this.props.page.unload(); + } - editGroupName = () => { - this.setState({ - editGroupName: true, - }, () => this.groupNameInput.focus()); - } + editGroupName = () => { + this.setState( + { + editGroupName: true, + }, + () => this.groupNameInput.focus() + ); + }; - handleGroupNameChange = (event) => { - this.setState({ - groupName: event.target.value, - }); - } + handleGroupNameChange = (event) => { + this.setState({ + groupName: event.target.value, + }); + }; - handleInputKeypress = (event) => { - if (event.key === "Enter") { - this.renameGroup(); - } else if (event.key === "Escape") { - this.cancelGroupRename(); - } - } + handleInputKeypress = (event) => { + if (event.key === "Enter") { + this.renameGroup(); + } else if (event.key === "Escape") { + this.cancelGroupRename(); + } + }; - stopLoading = () => { - this.setState({ - loading: false, - editGroupName: false, - }) - } + stopLoading = () => { + this.setState({ + loading: false, + editGroupName: false, + }); + }; - cancelGroupRename = () => { - this.setState({ - editGroupName: false, - groupName: this.state.originalGroupName, - }) - } + cancelGroupRename = () => { + this.setState({ + editGroupName: false, + groupName: this.state.originalGroupName, + }); + }; - renameGroup = () => { - if (!this.state.loading && this.state.groupName.replace(" ", "") !== "") { - if (this.state.groupName.trim() === this.state.originalGroupName) { - this.cancelGroupRename(); - } else { - this.setState({ - loading: true, - }); - - this.state.db.collection("groups") - .doc(this.props.match.params.groupId) - .update({ - display_name: this.state.groupName.trim(), - }).then(() => { - this.stopLoading(); - }).catch((error) => { - console.log(`Couldn't update group name: ${error}`); - this.setState({ - loading: false, - groupName: this.state.originalGroupName, - editGroupName: false, - }); - }); - } - } - } + renameGroup = () => { + if (!this.state.loading && this.state.groupName.replace(" ", "") !== "") { + if (this.state.groupName.trim() === this.state.originalGroupName) { + this.cancelGroupRename(); + } else { + this.setState({ + loading: true, + }); - removeSet = (setId) => { - let newLoadingState = { - sets: this.state.sets, - }; - newLoadingState.sets[setId].loading = true; - this.setState(newLoadingState); + this.state.db + .collection("groups") + .doc(this.props.match.params.groupId) + .update({ + display_name: this.state.groupName.trim(), + }) + .then(() => { + this.stopLoading(); + }) + .catch((error) => { + console.log(`Couldn't update group name: ${error}`); + this.setState({ + loading: false, + groupName: this.state.originalGroupName, + editGroupName: false, + }); + }); + } + } + }; - this.state.functions.removeSetFromGroup({ - groupId: this.props.match.params.groupId, - setId: setId, - }).then(() => { - let newState = { - sets: this.state.sets, - }; - delete newState.sets[setId]; - this.setState(newState); - }).catch((error) => { - console.log(`Can't remove set from group: ${error}`); - newLoadingState.sets[setId].loading = false; - this.setState(newLoadingState); - }); - } + removeSet = (setId) => { + let newLoadingState = { + sets: this.state.sets, + }; + newLoadingState.sets[setId].loading = true; + this.setState(newLoadingState); - showEditUserRole = (role, index) => { - let user; - if (role === "owner") { - user = this.state.groupUsers.owners[index]; - } else if (role === "contributor") { - user = this.state.groupUsers.contributors[index]; - } else { - user = this.state.groupUsers.members[index]; - } - this.setState({ - editingUser: { - uid: user.uid, - role: role, - index: index, - }, - }); - } + this.state.functions + .removeSetFromGroup({ + groupId: this.props.match.params.groupId, + setId: setId, + }) + .then(() => { + let newState = { + sets: this.state.sets, + }; + delete newState.sets[setId]; + this.setState(newState); + }) + .catch((error) => { + console.log(`Can't remove set from group: ${error}`); + newLoadingState.sets[setId].loading = false; + this.setState(newLoadingState); + }); + }; - hideEditUserRole = () => { - this.setState({ - editingUser: null, - }); - } + showEditUserRole = (role, index) => { + let user; + if (role === "owner") { + user = this.state.groupUsers.owners[index]; + } else if (role === "contributor") { + user = this.state.groupUsers.contributors[index]; + } else { + user = this.state.groupUsers.members[index]; + } + this.setState({ + editingUser: { + uid: user.uid, + role: role, + index: index, + }, + }); + }; - editUserRole = (role) => { - if (role === this.state.editingUser.role) { - this.setState({ - editingUser: null, - }); - } else { - if (role === "remove") { - this.state.db.collection("users") - .doc(this.state.editingUser.uid) - .collection("groups") - .doc(this.props.match.params.groupId) - .delete() - .then(() => { - let groupUsers = this.state.groupUsers; - if (this.state.editingUser.role === "owner") { - groupUsers.owners.splice(this.state.editingUser.index, 1); - } else if (this.state.editingUser.role === "contributor") { - groupUsers.contributors.splice(this.state.editingUser.index, 1); - } else { - groupUsers.members.splice(this.state.editingUser.index, 1); - } - this.setState({ - editingUser: null, - groupUsers: groupUsers, - }); - }).catch((error) => { - this.setState({ - editingUser: null, - }); - console.log(`Couldn't change user role: ${error}`) - }); - } else { - this.state.db.collection("users") - .doc(this.state.editingUser.uid) - .collection("groups") - .doc(this.props.match.params.groupId) - .update({ - role: role, - }).then(() => { - let groupUsers = this.state.groupUsers; - let userData; - if (this.state.editingUser.role === "owner") { - userData = groupUsers.owners.splice(this.state.editingUser.index, 1)[0]; - } else if (this.state.editingUser.role === "contributor") { - userData = groupUsers.contributors.splice(this.state.editingUser.index, 1)[0]; - } else { - userData = groupUsers.members.splice(this.state.editingUser.index, 1)[0]; - } - if (role === "owner") { - groupUsers.owners.push(userData); - } else if (role === "contributor") { - groupUsers.contributors.push(userData); - } else { - groupUsers.members.push(userData); - } - this.setState({ - editingUser: null, - groupUsers: groupUsers, - }); - }).catch((error) => { - this.setState({ - editingUser: null, - }); - console.log(`Couldn't change user role: ${error}`) - }); - } - } - } + hideEditUserRole = () => { + this.setState({ + editingUser: null, + }); + }; - showDeleteGroup = () => { - this.setState({ - showDeleteGroup: true, - }); - } + editUserRole = (role) => { + if (role === this.state.editingUser.role) { + this.setState({ + editingUser: null, + }); + } else { + if (role === "remove") { + this.state.db + .collection("users") + .doc(this.state.editingUser.uid) + .collection("groups") + .doc(this.props.match.params.groupId) + .delete() + .then(() => { + let groupUsers = this.state.groupUsers; + if (this.state.editingUser.role === "owner") { + groupUsers.owners.splice(this.state.editingUser.index, 1); + } else if (this.state.editingUser.role === "contributor") { + groupUsers.contributors.splice(this.state.editingUser.index, 1); + } else { + groupUsers.members.splice(this.state.editingUser.index, 1); + } + this.setState({ + editingUser: null, + groupUsers: groupUsers, + }); + }) + .catch((error) => { + this.setState({ + editingUser: null, + }); + console.log(`Couldn't change user role: ${error}`); + }); + } else { + this.state.db + .collection("users") + .doc(this.state.editingUser.uid) + .collection("groups") + .doc(this.props.match.params.groupId) + .update({ + role: role, + }) + .then(() => { + let groupUsers = this.state.groupUsers; + let userData; + if (this.state.editingUser.role === "owner") { + userData = groupUsers.owners.splice(this.state.editingUser.index, 1)[0]; + } else if (this.state.editingUser.role === "contributor") { + userData = groupUsers.contributors.splice(this.state.editingUser.index, 1)[0]; + } else { + userData = groupUsers.members.splice(this.state.editingUser.index, 1)[0]; + } + if (role === "owner") { + groupUsers.owners.push(userData); + } else if (role === "contributor") { + groupUsers.contributors.push(userData); + } else { + groupUsers.members.push(userData); + } + this.setState({ + editingUser: null, + groupUsers: groupUsers, + }); + }) + .catch((error) => { + this.setState({ + editingUser: null, + }); + console.log(`Couldn't change user role: ${error}`); + }); + } + } + }; - hideDeleteGroup = () => { - this.setState({ - showDeleteGroup: false, - }); - } + showDeleteGroup = () => { + this.setState({ + showDeleteGroup: true, + }); + }; - deleteGroup = () => { - this.setState({ - deleteGroupLoading: true, - }); + hideDeleteGroup = () => { + this.setState({ + showDeleteGroup: false, + }); + }; - this.state.db.collection("groups") - .doc(this.props.match.params.groupId) - .delete() - .then(() => { - this.props.history.push("/groups"); - }).catch((error) => { - console.log(`Couldn't delete group: ${error}`); - this.setState({ - deleteGroupLoading: false, - }); - }) - } + deleteGroup = () => { + this.setState({ + deleteGroupLoading: true, + }); - render() { - return ( - (this.state.role === "none") ? - - : -
- -
- { - !(this.state.role === null) && - <> -
- { - this.state.editGroupName && this.state.role === "owner" - ? -

- (this.groupNameInput = inputEl)} - autoComplete="off" - /> - -

- : -

{}}> - {this.state.groupName} - { - this.state.role === "owner" && - - - - } -

- } - { - this.state.role === "owner" && -
- } - className="button--round" - title="Group progress" - > - -
- } -
- { - this.state.joinCode && -
-

Join code

-

{this.state.joinCode}

-
- } - { - this.state.memberCount && -
-

{this.state.memberCount}

-

- member - { this.state.memberCount !== 1 && "s" } -

-
- } - -
-

Sets

- { - Object.keys(this.state.sets).length > 0 - ? -
- { - Object.keys(this.state.sets) - .sort((a, b) => { - if (this.state.sets[a].displayName < this.state.sets[b].displayName) { - return -1; - } - if (this.state.sets[a].displayName > this.state.sets[b].displayName) { - return 1; - } - return 0; - }) - .map((setId) => -
- - {this.state.sets[setId].displayName} - - { - this.state.role === "owner" && - - } -
- ) - } -
- : -

- This group doesn't have any sets yet! -

- } -
- { - this.state.role === "owner" && - <> -
-

Members

- { - this.state.groupUsers && this.state.groupUsers.owners.length > 0 && - <> -

Owners

-
- { - this.state.groupUsers.owners.map((user, index) => -

{ } : () => this.showEditUserRole("owner", index)} - > - { - user.uid === this.state.user.uid - ? - "You" - : - <> - {user.displayName} - - - } -

- ) - } -
- - } - { - this.state.groupUsers && this.state.groupUsers.contributors.length > 0 && - <> -

Contributors

-
- { - this.state.groupUsers.contributors.map((user, index) => -

{ } : () => this.showEditUserRole("contributor", index)} - > - { - user.uid === this.state.user.uid - ? - "You" - : - <> - {user.displayName} - - - } -

- ) - } -
- - } - { - this.state.groupUsers && this.state.groupUsers.members.length > 0 && - <> -

Members

-
- { - this.state.groupUsers.members.map((user, index) => -

{ } : () => this.showEditUserRole("member", index)} - > - { - user.uid === this.state.user.uid - ? - "You" - : - <> - {user.displayName} - - - } -

- ) - } -
- - } -
- { - this.state.editingUser && - <> -
-
- { - ["Owner", "Contributor", "Member", "Remove"].map((role) => -

this.editUserRole(role.toLowerCase())} - > - {role} -

- ) - } - -
- Cancel -
-
- - } - { - this.state.showDeleteGroup && - - } - - } - - } -
-
-
- ) - } -}) + this.state.db + .collection("groups") + .doc(this.props.match.params.groupId) + .delete() + .then(() => { + this.props.history.push("/groups"); + }) + .catch((error) => { + console.log(`Couldn't delete group: ${error}`); + this.setState({ + deleteGroupLoading: false, + }); + }); + }; + + showLeaveGroup = () => { + this.setState({ + showLeaveGroup: true, + }); + }; + + hideLeaveGroup = () => { + this.setState({ + showLeaveGroup: false, + }); + }; + + leaveGroup = () => { + this.setState({ + leaveGroupLoading: true, + }); + + this.state.db + .collection("users") + .doc(this.props.user.uid) + .collection("groups") + .doc(this.props.match.params.groupId) + .delete() + .then(() => { + this.props.history.push("/groups"); + }) + .catch((error) => { + console.log(`Couldn't leave group: ${error}`); + this.setState({ + leaveGroupLoading: false, + }); + }); + }; + + render() { + return this.state.role === "none" ? ( + + ) : ( +
+ +
+ {!(this.state.role === null) && ( + <> +
+ {this.state.editGroupName && this.state.role === "owner" ? ( +

+ (this.groupNameInput = inputEl)} + autoComplete="off" + /> + +

+ ) : ( +

{}}> + {this.state.groupName} + {this.state.role === "owner" && ( + + + + )} +

+ )} + {this.state.role === "owner" ? ( +
+ } + className="button--round" + title="Group progress" + > + +
+ ) : ( +
+ +
+ )} +
+ {this.state.joinCode && ( +
+

Join code

+

{this.state.joinCode}

+
+ )} + {this.state.memberCount && ( +
+

{this.state.memberCount}

+

+ member + {this.state.memberCount !== 1 && "s"} +

+
+ )} + +
+

Sets

+ {Object.keys(this.state.sets).length > 0 ? ( +
+ {Object.keys(this.state.sets) + .sort((a, b) => { + if (this.state.sets[a].displayName < this.state.sets[b].displayName) { + return -1; + } + if (this.state.sets[a].displayName > this.state.sets[b].displayName) { + return 1; + } + return 0; + }) + .map((setId) => ( +
+ {this.state.sets[setId].displayName} + {this.state.role === "owner" && ( + + )} +
+ ))} +
+ ) : ( +

This group doesn't have any sets yet!

+ )} +
+ {this.state.role === "owner" && ( + <> +
+

Members

+ {this.state.groupUsers && this.state.groupUsers.owners.length > 0 && ( + <> +

Owners

+
+ {this.state.groupUsers.owners.map((user, index) => ( +

{} + : () => this.showEditUserRole("owner", index) + } + > + {user.uid === this.state.user.uid ? ( + "You" + ) : ( + <> + {user.displayName} + + + )} +

+ ))} +
+ + )} + {this.state.groupUsers && this.state.groupUsers.contributors.length > 0 && ( + <> +

Contributors

+
+ {this.state.groupUsers.contributors.map((user, index) => ( +

{} + : () => this.showEditUserRole("contributor", index) + } + > + {user.uid === this.state.user.uid ? ( + "You" + ) : ( + <> + {user.displayName} + + + )} +

+ ))} +
+ + )} + {this.state.groupUsers && this.state.groupUsers.members.length > 0 && ( + <> +

Members

+
+ {this.state.groupUsers.members.map((user, index) => ( +

{} + : () => this.showEditUserRole("member", index) + } + > + {user.uid === this.state.user.uid ? ( + "You" + ) : ( + <> + {user.displayName} + + + )} +

+ ))} +
+ + )} +
+ {this.state.editingUser && ( + <> +
+
+ {["Owner", "Contributor", "Member", "Remove"].map((role) => ( +

this.editUserRole(role.toLowerCase())}> + {role} +

+ ))} + +
Cancel
+
+ + )} + {this.state.showDeleteGroup && ( + + )} + + )} + {this.state.showLeaveGroup && ( + + )} + + )} +
+
+ ); + } + } +); diff --git a/test/firestore.test.js b/test/firestore.test.js index aaa4811..3aa18e0 100644 --- a/test/firestore.test.js +++ b/test/firestore.test.js @@ -117,18 +117,42 @@ describe("Parandum Firestore database", () => { await firebase.assertSucceeds(testDoc.get()); }); - it("Can delete current user's groups", async () => { + it("Can delete current user's groups when not group owner", async () => { + const admin = getAdminFirestore(); + await admin.collection("users").doc(myId).collection("groups").doc(groupOne).set({ role: "member" }); + const db = getFirestore(myAuth); const testDoc = db.collection("users").doc(myId).collection("groups").doc(groupOne); await firebase.assertSucceeds(testDoc.delete()); }); - it("Can't delete other users' groups", async () => { + it("Can't delete current user's groups when group owner", async () => { + const admin = getAdminFirestore(); + await admin.collection("users").doc(myId).collection("groups").doc(groupOne).set({ role: "owner" }); + + const db = getFirestore(myAuth); + const testDoc = db.collection("users").doc(myId).collection("groups").doc(groupOne); + await firebase.assertFails(testDoc.delete()); + }); + + it("Can't delete other users' groups when not group owner", async () => { + const admin = getAdminFirestore(); + await admin.collection("users").doc(myId).collection("groups").doc(groupOne).set({ role: "member" }); + const db = getFirestore(myAuth); const testDoc = db.collection("users").doc(theirId).collection("groups").doc(groupOne); await firebase.assertFails(testDoc.delete()); }); + it("Can delete other users' groups when group owner", async () => { + const admin = getAdminFirestore(); + await admin.collection("users").doc(myId).collection("groups").doc(groupOne).set({ role: "owner" }); + + const db = getFirestore(myAuth); + const testDoc = db.collection("users").doc(theirId).collection("groups").doc(groupOne); + await firebase.assertSucceeds(testDoc.delete()); + }); + it("Can delete other users' groups when admin", async () => { const db = getFirestore(myAdminAuth); const testDoc = db.collection("users").doc(theirId).collection("groups").doc(groupOne);