const test = require('firebase-functions-test')({ databaseURL: 'https://parandum-learning-dev.firebaseio.com', storageBucket: 'parandum-learning-dev.appspot.com', projectId: 'parandum-learning-dev', }, '_private_stuff/parandum-learning-dev-private-key.json'); const admin = require("firebase-admin"); const cloudFunctions = require('../functions/index.js'); const firebase = require("@firebase/rules-unit-testing"); const hamjest = require("hamjest"); const assert = require("assert"); admin.initializeApp(); const firestore = admin.firestore(); const userOne = "M3JPrFRH6Fdo8XMUbF0l2zVZUCH3"; const userTwo = "user_02"; const setOne = "set_01"; const setTwo = "set_02"; const vocabOne = "vocab_01"; const termOne = "term_01"; const definitionOne = "definition_01"; const soundOne = true; const vocabTwo = "vocab_02"; const termTwo = "term_02"; const definitionTwo = "definition_02"; const soundTwo = true; const groupOne = "group_01"; const doubleDefinitionOne = "definition/01"; const doubleDefinitionTwo = "definition/02"; const punctuationDefinitionOne = "definition .,()-_'\"01"; const progressVocabOne = userOne + "__" + vocabOne; const progressVocabTwo = userOne + "__" + vocabTwo; const vocabThree = "vocab_03"; const vocabFour = "vocab_04"; const progressVocabThree = userOne + "__" + vocabThree; const progressVocabFour = userOne + "__" + vocabFour; describe("Parandum Cloud Functions", () => { it("Can write & delete to/from online database", async () => { firebase.assertSucceeds( firestore.collection("testCollection").doc("testDoc").set({ "one": "1", "two": "2", }) ); firebase.assertSucceeds( firestore.collection("testCollection").doc("testDoc").delete() ); }); it("createProgress can create new questions mode progress file from existing set", async () => { const createProgress = test.wrap(cloudFunctions.createProgress); const setDataOne = { "owner": userOne, "public": false, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); const requestData = { switch_language: false, sets: [setOne], mode: "questions", limit: 2, }; const progressId = await createProgress(requestData); const progressDocId = firestore.collection("progress").doc(progressId); const snapAfter = await progressDocId.get().then((doc) => doc.data()); const termOneSnapAfter = await progressDocId.collection("terms").doc(progressVocabOne).get().then((doc) => doc.data()); const definitionOneSnapAfter = await progressDocId.collection("definitions").doc(progressVocabOne).get().then((doc) => doc.data()); hamjest.assertThat(snapAfter.questions, hamjest.anyOf( hamjest.is([progressVocabOne, progressVocabTwo]), hamjest.is([progressVocabTwo, progressVocabOne]) )); assert.deepStrictEqual(snapAfter.correct, []); assert.deepStrictEqual(snapAfter.incorrect, []); assert.deepStrictEqual(snapAfter.current_correct, []); assert.strictEqual(snapAfter.duration, null); assert.strictEqual(snapAfter.progress, 0); assert.deepStrictEqual(snapAfter.setIds, [setOne]); assert.strictEqual(snapAfter.set_title, setOne); assert.notStrictEqual(snapAfter.start_time, null); assert.strictEqual(snapAfter.switch_language, false); assert.strictEqual(snapAfter.uid, userOne); assert.strictEqual(snapAfter.mode, "questions"); assert.strictEqual(snapAfter.typo, false); assert.deepStrictEqual(termOneSnapAfter, { item: termOne, sound: soundOne, }); assert.deepStrictEqual(definitionOneSnapAfter, { item: definitionOne, sound: soundOne, }); }); it("createProgress can create new questions mode progress file from multiple existing sets", async () => { const createProgress = test.wrap(cloudFunctions.createProgress); const setDataOne = { "owner": userOne, "public": false, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; const setDataTwo = { "owner": userOne, "public": false, "title": setTwo, }; const vocabDataThree = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataFour = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("sets").doc(setTwo).set(setDataTwo); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabOne).delete(); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabTwo).delete(); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabThree).set(vocabDataThree); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabFour).set(vocabDataFour); const requestData = { switch_language: false, sets: [setOne, setTwo], mode: "questions", limit: 4, }; const progressId = await createProgress(requestData); const progressDocId = firestore.collection("progress").doc(progressId); const snapAfter = await progressDocId.get().then((doc) => doc.data()); assert.deepStrictEqual(snapAfter.questions.sort(), [progressVocabOne, progressVocabTwo, progressVocabThree, progressVocabFour]) assert.deepStrictEqual(snapAfter.correct, []); assert.deepStrictEqual(snapAfter.incorrect, []); assert.deepStrictEqual(snapAfter.current_correct, []); assert.strictEqual(snapAfter.duration, null); assert.strictEqual(snapAfter.progress, 0); assert.deepStrictEqual(snapAfter.setIds, [setOne, setTwo]); assert.strictEqual(snapAfter.set_title, `${setOne} & ${setTwo}`); assert.notStrictEqual(snapAfter.start_time, null); assert.strictEqual(snapAfter.switch_language, false); assert.strictEqual(snapAfter.uid, userOne); assert.strictEqual(snapAfter.mode, "questions"); assert.strictEqual(snapAfter.typo, false); }); it("createProgress can create new lives mode progress file from existing set", async () => { const createProgress = test.wrap(cloudFunctions.createProgress); const setDataOne = { "owner": userOne, "public": false, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); const requestData = { switch_language: false, sets: [setOne], mode: "lives", limit: 2, }; const progressId = await createProgress(requestData); const progressDocId = firestore.collection("progress").doc(progressId); const snapAfter = await progressDocId.get().then((doc) => doc.data()); hamjest.assertThat(snapAfter.questions, hamjest.anyOf( hamjest.is([progressVocabOne, progressVocabTwo]), hamjest.is([progressVocabTwo, progressVocabOne]) )); assert.deepStrictEqual(snapAfter.correct, []); assert.deepStrictEqual(snapAfter.incorrect, []); assert.deepStrictEqual(snapAfter.current_correct, []); assert.strictEqual(snapAfter.duration, null); assert.strictEqual(snapAfter.progress, 0); assert.deepStrictEqual(snapAfter.setIds, [setOne]); assert.strictEqual(snapAfter.set_title, setOne); assert.notStrictEqual(snapAfter.start_time, null); assert.strictEqual(snapAfter.switch_language, false); assert.strictEqual(snapAfter.uid, userOne); assert.strictEqual(snapAfter.mode, "lives"); assert.strictEqual(snapAfter.lives, 2); assert.strictEqual(snapAfter.typo, false); }); it("createProgress can create new progress file from public set they aren't the owner of", async () => { const createProgress = test.wrap(cloudFunctions.createProgress); const setDataTwo = { "owner": userTwo, "public": true, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; await firestore.collection("sets").doc(setTwo).set(setDataTwo); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); const requestData = { switch_language: false, sets: [setOne], mode: "questions", limit: 2, }; firebase.assertSucceeds(createProgress(requestData)); }); it("createProgress can't create new progress file from non-public set they aren't the owner of", async () => { const createProgress = test.wrap(cloudFunctions.createProgress); const setDataTwo = { "owner": userTwo, "public": false, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; await firestore.collection("sets").doc(setTwo).set(setDataTwo); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setTwo) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); const requestData = { switch_language: false, sets: [setTwo], mode: "questions", limit: 2, }; firebase.assertFails(createProgress(requestData)); }); it("processAnswer updates progress documents appropriately when correct and incorrect answers provided", async () => { const processAnswer = test.wrap(cloudFunctions.processAnswer); const progressData = { correct: [], current_correct: [], duration: null, incorrect: [], progress: 0, questions: [ progressVocabOne, progressVocabTwo ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }; const termDataOne = { "item": termOne, "sound": soundOne, }; const termDataTwo = { "item": termTwo, "sound": soundTwo, }; const definitionDataOne = { "item": definitionOne, "sound": soundOne, }; const definitionDataTwo = { "item": definitionTwo, "sound": soundTwo, }; const progressId = "progress_01"; const progressDocId = firestore.collection("progress").doc(progressId); await progressDocId.set(progressData); await progressDocId.collection("terms").doc(progressVocabOne) .set(termDataOne); await progressDocId.collection("terms").doc(progressVocabTwo) .set(termDataTwo); await progressDocId.collection("definitions").doc(progressVocabOne) .set(definitionDataOne); await progressDocId.collection("definitions").doc(progressVocabTwo) .set(definitionDataTwo); const firstTermAnswerRequestData = { progressId: progressId, answer: "definition_01", }; const secondTermAnswerRequestData = { progressId: progressId, answer: "definition_02", }; const firstReturn = await processAnswer(secondTermAnswerRequestData); hamjest.assertThat(firstReturn, hamjest.anyOf( hamjest.is({ mode: "questions", correct: false, correctAnswers: [definitionOne], currentVocabId: progressVocabOne, moreAnswers: false, nextPrompt: { item: termOne, sound: soundOne, set_owner: userOne, }, progress: 1, totalQuestions: 3, totalCorrect: 0, totalIncorrect: 1, typo: false, }), hamjest.is({ mode: "questions", correct: false, correctAnswers: [definitionOne], currentVocabId: progressVocabOne, moreAnswers: false, nextPrompt: { item: termTwo, sound: soundTwo, set_owner: userOne, }, progress: 1, totalQuestions: 3, totalCorrect: 0, totalIncorrect: 1, typo: false, }) )); const snapAfterIncorrectData = await progressDocId.get().then((doc) => doc.data()); hamjest.assertThat(snapAfterIncorrectData, hamjest.anyOf( hamjest.is({ correct: [], current_correct: [], duration: null, incorrect: [progressVocabOne], progress: 1, questions: [ progressVocabOne, progressVocabOne, progressVocabTwo ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }), hamjest.is({ correct: [], current_correct: [], duration: null, incorrect: [progressVocabOne], progress: 1, questions: [ progressVocabOne, progressVocabTwo, progressVocabOne ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }) )); if (firstReturn.nextPrompt.item === "term_01") { await processAnswer(firstTermAnswerRequestData); await processAnswer(secondTermAnswerRequestData); } else { await processAnswer(secondTermAnswerRequestData); await processAnswer(firstTermAnswerRequestData); } const snapAfterCorrectData = await progressDocId.get().then((doc) => doc.data()); hamjest.assertThat(snapAfterCorrectData.correct, hamjest.anyOf( hamjest.is([progressVocabOne, progressVocabTwo]), hamjest.is([progressVocabTwo, progressVocabOne]) )); hamjest.assertThat(snapAfterCorrectData.questions, hamjest.anyOf( hamjest.is([progressVocabOne, progressVocabOne, progressVocabTwo]), hamjest.is([progressVocabOne, progressVocabTwo, progressVocabOne]) )); assert.deepStrictEqual(snapAfterCorrectData.incorrect, [progressVocabOne]); assert.deepStrictEqual(snapAfterCorrectData.current_correct, []); assert.notStrictEqual(snapAfterCorrectData.duration, null); assert.strictEqual(snapAfterCorrectData.progress, 3); assert.strictEqual(snapAfterCorrectData.set_title, setOne); assert.strictEqual(snapAfterCorrectData.start_time, 1627308670962); assert.strictEqual(snapAfterCorrectData.switch_language, false); assert.strictEqual(snapAfterCorrectData.uid, userOne); assert.strictEqual(snapAfterCorrectData.mode, "questions"); assert.strictEqual(snapAfterCorrectData.typo, false); }); it("processAnswer returns correct data", async () => { const processAnswer = test.wrap(cloudFunctions.processAnswer); const progressData = { correct: [], current_correct: [], duration: null, incorrect: [], progress: 0, questions: [ progressVocabOne ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }; const termDataOne = { "item": termOne, "sound": soundOne, }; const definitionDataOne = { "item": definitionOne, "sound": soundOne, }; const progressId = "progress_01"; const progressDocId = firestore.collection("progress").doc(progressId); await progressDocId.set(progressData); await progressDocId.collection("terms").doc(progressVocabOne) .set(termDataOne); await progressDocId.collection("definitions").doc(progressVocabOne) .set(definitionDataOne); const correctAnswerRequestData = { progressId: progressId, answer: "definition_01", }; const incorrectAnswerRequestData = { progressId: progressId, answer: "definition_02", }; const returnAfterIncorrect = await processAnswer(incorrectAnswerRequestData); const returnAfterCorrect = await processAnswer(correctAnswerRequestData); assert.deepStrictEqual(returnAfterIncorrect, { mode: "questions", correct: false, correctAnswers: [definitionOne], currentVocabId: progressVocabOne, moreAnswers: false, nextPrompt: { item: termOne, sound: soundOne, set_owner: userOne, }, progress: 1, totalQuestions: 2, totalCorrect: 0, totalIncorrect: 1, typo: false, }); assert.strictEqual(returnAfterCorrect.mode, "questions"); assert.strictEqual(returnAfterCorrect.correct, true) assert.deepStrictEqual(returnAfterCorrect.correctAnswers, [definitionOne]); assert.strictEqual(returnAfterCorrect.currentVocabId, progressVocabOne); assert.notStrictEqual(returnAfterCorrect.duration, null); assert.deepStrictEqual(returnAfterCorrect.incorrectAnswers, [progressVocabOne]); assert.strictEqual(returnAfterCorrect.moreAnswers, false); assert.strictEqual(returnAfterCorrect.nextPrompt, null); assert.strictEqual(returnAfterCorrect.progress, 2); assert.strictEqual(returnAfterCorrect.totalQuestions, 2); assert.strictEqual(returnAfterCorrect.totalCorrect, 1); assert.strictEqual(returnAfterCorrect.totalIncorrect, 1); assert.strictEqual(returnAfterCorrect.mode, "questions"); }); it("processAnswer correctly handles correct and incorrect inputted answers when a vocab term has multiple required answers", async () => { const processAnswer = test.wrap(cloudFunctions.processAnswer); const progressData = { correct: [], duration: null, incorrect: [], current_correct: [], progress: 0, questions: [ progressVocabOne, progressVocabTwo ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }; const termDataOne = { "item": termOne, "sound": soundOne, }; const termDataTwo = { "item": termTwo, "sound": soundTwo, }; const definitionDataOne = { "item": doubleDefinitionOne, "sound": soundOne, }; const definitionDataTwo = { "item": doubleDefinitionTwo, "sound": soundTwo, }; const progressId = "progress_01"; const progressDocId = firestore.collection("progress").doc(progressId); await progressDocId.set(progressData); await progressDocId.collection("terms").doc(progressVocabOne) .set(termDataOne); await progressDocId.collection("terms").doc(progressVocabTwo) .set(termDataTwo); await progressDocId.collection("definitions").doc(progressVocabOne) .set(definitionDataOne); await progressDocId.collection("definitions").doc(progressVocabTwo) .set(definitionDataTwo); const firstTermAnswerOneRequestData = { progressId: progressId, answer: "definition", }; const firstTermAnswerTwoRequestData = { progressId: progressId, answer: "01", }; const secondTermAnswerOneRequestData = { progressId: progressId, answer: "definition", }; const secondTermAnswerTwoRequestData = { progressId: progressId, answer: "02", }; const returnAfterCorrect = await processAnswer(firstTermAnswerOneRequestData); assert.deepStrictEqual(returnAfterCorrect, { mode: "questions", correct: true, correctAnswers: ["definition"], currentVocabId: progressVocabOne, moreAnswers: true, nextPrompt: null, progress: 0, totalQuestions: 2, totalCorrect: 0, totalIncorrect: 0, typo: false, }); const snapAfterTermOneAnswerOneData = await progressDocId.get().then((doc) => doc.data()); assert.deepStrictEqual(snapAfterTermOneAnswerOneData, { correct: [], current_correct: ["definition"], duration: null, incorrect: [], progress: 0, questions: [ progressVocabOne, progressVocabTwo ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }); const returnAfterIncorrect = await processAnswer(secondTermAnswerTwoRequestData); const snapAfterIncorrectData = await progressDocId.get().then((doc) => doc.data()); hamjest.assertThat(snapAfterIncorrectData, hamjest.anyOf( hamjest.is({ correct: [], current_correct: [], duration: null, incorrect: [progressVocabOne], progress: 1, questions: [ progressVocabOne, progressVocabOne, progressVocabTwo ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }), hamjest.is({ correct: [], current_correct: [], duration: null, incorrect: [progressVocabOne], progress: 1, questions: [ progressVocabOne, progressVocabTwo, progressVocabOne ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }) )); if (returnAfterIncorrect.nextPrompt.item === "term_01") { await processAnswer(firstTermAnswerOneRequestData); await processAnswer(firstTermAnswerTwoRequestData); await processAnswer(secondTermAnswerOneRequestData); await processAnswer(secondTermAnswerTwoRequestData); } else { await processAnswer(secondTermAnswerOneRequestData); await processAnswer(secondTermAnswerTwoRequestData); await processAnswer(firstTermAnswerOneRequestData); await processAnswer(firstTermAnswerTwoRequestData); } const snapAfterCorrectData = await progressDocId.get().then((doc) => doc.data()); hamjest.assertThat(snapAfterCorrectData.correct, hamjest.anyOf( hamjest.is([progressVocabOne, progressVocabTwo]), hamjest.is([progressVocabTwo, progressVocabOne]) )); hamjest.assertThat(snapAfterCorrectData.questions, hamjest.anyOf( hamjest.is([progressVocabOne, progressVocabOne, progressVocabTwo]), hamjest.is([progressVocabOne, progressVocabTwo, progressVocabOne]) )); assert.deepStrictEqual(snapAfterCorrectData.incorrect, [progressVocabOne]); assert.deepStrictEqual(snapAfterCorrectData.current_correct, []); assert.notStrictEqual(snapAfterCorrectData.duration, null); assert.strictEqual(snapAfterCorrectData.progress, 3); assert.strictEqual(snapAfterCorrectData.set_title, setOne); assert.strictEqual(snapAfterCorrectData.start_time, 1627308670962); assert.strictEqual(snapAfterCorrectData.switch_language, false); assert.strictEqual(snapAfterCorrectData.uid, userOne); assert.strictEqual(snapAfterCorrectData.mode, "questions"); assert.strictEqual(snapAfterCorrectData.typo, false); }).timeout(5000); it("processAnswer ignores punctuation", async () => { const processAnswer = test.wrap(cloudFunctions.processAnswer); const progressData = { correct: [], current_correct: [], duration: null, incorrect: [], progress: 0, questions: [ progressVocabOne, progressVocabTwo ], set_title: setOne, start_time: 1627308670962, switch_language: false, uid: userOne, mode: "questions", typo: false, }; const termDataOne = { "item": termOne, "sound": soundOne, }; const termDataTwo = { "item": termTwo, "sound": soundTwo, }; const definitionDataOne = { "item": punctuationDefinitionOne, "sound": soundOne, }; const definitionDataTwo = { "item": definitionTwo, "sound": soundTwo, }; const progressId = "progress_01"; const progressDocId = firestore.collection("progress").doc(progressId); await progressDocId.set(progressData); await progressDocId.collection("terms").doc(progressVocabOne) .set(termDataOne); await progressDocId.collection("terms").doc(progressVocabTwo) .set(termDataTwo); await progressDocId.collection("definitions").doc(progressVocabOne) .set(definitionDataOne); await progressDocId.collection("definitions").doc(progressVocabTwo) .set(definitionDataTwo); const requestData = { progressId: progressId, answer: "definition\"'_-)(,.0 1", }; const returnedData = await processAnswer(requestData); assert.equal(returnedData.correct, true); }); it("setAdmin can change other users' admin states", async () => { /** NOTE * Admin uid is M3JPrFRH6Fdo8XMUbF0l2zVZUCH3. * This uid should be set in the function's code during testing. */ const setAdmin = test.wrap(cloudFunctions.setAdmin); const targetId = await admin.auth().createUser({ email: "user_01@mgrove.uk", password: "user1234", }).then((user) => user.uid); firebase.assertSucceeds(await setAdmin({ targetUser: targetId, adminState: true, })); await admin.auth().deleteUser(targetId); }); it("setAdmin can't change current user's admin state", () => { /** NOTE * Admin uid is M3JPrFRH6Fdo8XMUbF0l2zVZUCH3. * This uid should be set in the function's code during testing. */ const setAdmin = test.wrap(cloudFunctions.setAdmin); const targetId = "M3JPrFRH6Fdo8XMUbF0l2zVZUCH3"; firebase.assertFails(setAdmin({ targetUser: targetId, adminState: false, })); }); it("addSetToGroup can add existing set to existing group", async () => { const addSetToGroup = test.wrap(cloudFunctions.addSetToGroup); const setDataOne = { "owner": userOne, "public": true, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; let groupUsers = {}; groupUsers[userOne] = "owner"; const groupDataOne = { "display_name": groupOne, "join_code": "abcd1234", "sets": [], "users": groupUsers, }; const userGroupDataOne = { role: "owner", }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("groups").doc(groupOne).set(groupDataOne); await firestore.collection("users").doc(userOne) .collection("groups").doc(groupOne).set(userGroupDataOne); firebase.assertSucceeds(addSetToGroup({ groupId: groupOne, setId: setOne, })); }); it("addSetToGroup can't add existing set that's not public and isn't theirs to existing group", async () => { const addSetToGroup = test.wrap(cloudFunctions.addSetToGroup); const setDataOne = { "owner": userTwo, "public": false, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; let groupUsers = {}; groupUsers[userOne] = "owner"; const groupDataOne = { "display_name": groupOne, "join_code": "abcd1234", "sets": [], "users": groupUsers, }; const userGroupDataOne = { role: "owner", }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("groups").doc(groupOne).set(groupDataOne); await firestore.collection("users").doc(userOne) .collection("groups").doc(groupOne).set(userGroupDataOne); firebase.assertFails(addSetToGroup({ groupId: groupOne, setId: setOne, })); }); it("addSetToGroup can't add existing set to existing group when their role is member and they aren't admin", async () => { const addSetToGroup = test.wrap(cloudFunctions.addSetToGroup); const setDataOne = { "owner": userOne, "public": true, "title": setOne, }; const vocabDataOne = { "term": termOne, "definition": definitionOne, "sound": soundOne, }; const vocabDataTwo = { "term": termTwo, "definition": definitionTwo, "sound": soundTwo, }; let groupUsers = {}; groupUsers[userOne] = "member"; const groupDataOne = { "display_name": groupOne, "join_code": "abcd1234", "sets": [], "users": groupUsers, }; const userGroupDataOne = { role: "member", }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("groups").doc(groupOne).set(groupDataOne); await firestore.collection("users").doc(userOne) .collection("groups").doc(groupOne).set(userGroupDataOne); firebase.assertFails(addSetToGroup({ groupId: groupOne, setId: setOne, })); }); it("removeSetFromGroup can remove existing set from existing group it is already a part of when group owner", async () => { const removeSetFromGroup = test.wrap(cloudFunctions.removeSetFromGroup); const setDataOne = { owner: userOne, public: true, title: setOne, groups: [groupOne], }; const vocabDataOne = { term: termOne, definition: definitionOne, sound: soundOne, }; const vocabDataTwo = { term: termTwo, definition: definitionTwo, sound: soundTwo, }; let groupUsers = {}; groupUsers[userOne] = "owner"; const groupDataOne = { display_name: groupOne, join_code: "abcd1234", sets: [setOne], users: groupUsers, }; const userGroupDataOne = { role: "owner", }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("groups").doc(groupOne).set(groupDataOne); await firestore.collection("users").doc(userOne) .collection("groups").doc(groupOne).set(userGroupDataOne); firebase.assertSucceeds(removeSetFromGroup({ groupId: groupOne, setId: setOne, })); }); it("removeSetFromGroup can't remove existing set from existing group it is already a part of when not group owner", async () => { const removeSetFromGroup = test.wrap(cloudFunctions.removeSetFromGroup); const setDataOne = { owner: userOne, public: true, title: setOne, groups: [groupOne], }; const vocabDataOne = { term: termOne, definition: definitionOne, sound: soundOne, }; const vocabDataTwo = { term: termTwo, definition: definitionTwo, sound: soundTwo, }; let groupUsers = {}; groupUsers[userOne] = "collaborator"; const groupDataOne = { display_name: groupOne, join_code: "abcd1234", sets: [setOne], users: groupUsers, }; const userGroupDataOne = { role: "collaborator", }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("groups").doc(groupOne).set(groupDataOne); await firestore.collection("users").doc(userOne) .collection("groups").doc(groupOne).set(userGroupDataOne); firebase.assertFails(removeSetFromGroup({ groupId: groupOne, setId: setOne, })); }); it("removeSetFromGroup can't remove existing set from existing group it is not already a part of when group owner", async () => { const removeSetFromGroup = test.wrap(cloudFunctions.removeSetFromGroup); const setDataOne = { owner: userOne, public: true, title: setOne, groups: [], }; const vocabDataOne = { term: termOne, definition: definitionOne, sound: soundOne, }; const vocabDataTwo = { term: termTwo, definition: definitionTwo, sound: soundTwo, }; let groupUsers = {}; groupUsers[userOne] = "owner"; const groupDataOne = { display_name: groupOne, join_code: "abcd1234", sets: [], users: groupUsers, }; const userGroupDataOne = { role: "owner", }; await firestore.collection("sets").doc(setOne).set(setDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabOne).set(vocabDataOne); await firestore.collection("sets").doc(setOne) .collection("vocab").doc(vocabTwo).set(vocabDataTwo); await firestore.collection("groups").doc(groupOne).set(groupDataOne); await firestore.collection("users").doc(userOne) .collection("groups").doc(groupOne).set(userGroupDataOne); firebase.assertFails(removeSetFromGroup({ groupId: groupOne, setId: setOne, })); }); it("createGroup can create new group", async () => { const createGroup = test.wrap(cloudFunctions.createGroup); const groupId = await createGroup(groupOne); const groupDocId = firestore.collection("groups").doc(groupId); const snapGroupAfter = await groupDocId.get().then((doc) => doc.data()); const userGroupDocId = firestore.collection("users").doc(userOne).collection("groups").doc(groupId); const joinCodeDocId = firestore.collection("join_codes").doc(snapGroupAfter.join_code); const snapUserGroupAfter = await userGroupDocId.get(); const joinCodeSnap = await joinCodeDocId.get(); assert.strictEqual(snapGroupAfter.display_name, groupOne); assert.deepStrictEqual(snapGroupAfter.sets, []); assert.deepStrictEqual(snapGroupAfter.users, {}); assert.notStrictEqual(snapGroupAfter.join_code, null); assert.deepStrictEqual(snapUserGroupAfter.data(), { role: "owner" }); assert.deepStrictEqual(joinCodeSnap.data(), { group: groupId, }); }); /*xit("getGroupMembers returns group members correctly", async () => { const getGroupMembers = test.wrap(cloudFunctions.getGroupMembers); const userGroupDataOne = { role: "owner", }; const userGroupDataTwo = { role: "collaborator", }; const userGroupDataThree = { role: "member", }; const userOneId = admin.auth().createUser({ email: "user_01@mgrove.uk", password: "user1234", displayName: "User 01", }).then((user) => { return user.uid; }); const userTwoId = admin.auth().createUser({ email: "user_02@mgrove.uk", password: "user1234", displayName: "User 02", }).then((user) => { return user.uid; }); const userThreeId = admin.auth().createUser({ email: "user_03@mgrove.uk", password: "user1234", displayName: "User 03", }).then((user) => { return user.uid; }); await Promise.all([ userOneId, userTwoId, userThreeId ]); const groupDataOne = { display_name: groupOne, join_code: "abcd1234", sets: [], users: { [userOneId]: "owner", [userTwoId]: "collaborator", [userThreeId]: "member", }, }; await Promise.all([ firestore.collection("groups").doc(groupOne).set(groupDataOne), firestore.collection("users").doc(userOneId) .collection("groups").doc(groupOne).set(userGroupDataOne), firestore.collection("users").doc(userTwoId) .collection("groups").doc(groupOne).set(userGroupDataTwo), firestore.collection("users").doc(userThreeId) .collection("groups").doc(groupOne).set(userGroupDataThree), ]); const returnData = await getGroupMembers(groupOne); await Promise.all([ admin.auth().deleteUser(userOneId), admin.auth().deleteUser(userTwoId), admin.auth().deleteUser(userThreeId), ]) assert.deepStrictEqual(returnData, { owners: [ { uid: userOneId, displayName: "User 01", } ], collaborators: [ { uid: userTwoId, displayName: "User 02", } ], members: [ { uid: userThreeId, displayName: "User 03", } ], }); });*/ });