Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

207 api getwhyranksandpointsdiscussionidround mostids leastids cb ranks points #245

174 changes: 174 additions & 0 deletions app/socket-apis/__tests__/get-why-ranks-and-points.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// https://github.com/EnCiv/civil-pursuit/issues/207

import getWhyRanksAndPoints from '../get-why-ranks-and-points'
import Points from '../../models/points'
import Ranks from '../../models/ranks'
import { Mongo } from '@enciv/mongo-collections'
import { MongoMemoryServer } from 'mongodb-memory-server'
import { ObjectId } from 'mongodb'
import { expect, test } from '@jest/globals'

let MemoryServer

beforeAll(async () => {
MemoryServer = await MongoMemoryServer.create()
const uri = MemoryServer.getUri()
await Mongo.connect(uri)
})

afterAll(async () => {
await Mongo.disconnect()
await MemoryServer.stop()
})

beforeEach(async () => {
jest.spyOn(console, 'error').mockImplementation(() => {})
await Points.deleteMany({})
await Ranks.deleteMany({})
})

afterEach(() => {
console.error.mockRestore()
})

// Config
const discussionId = '66a174b0c3f2051ad387d2a6'
const userId = '6667d5a33da5d19ddc304a6b'
const synuser = { synuser: { id: userId } }
const round = 1
const mostIds = [new ObjectId(), new ObjectId()]
const leastIds = [new ObjectId()]

// Test 1: User not logged in
test('Fail if user is not logged in', async () => {
const cb = jest.fn()
await getWhyRanksAndPoints.call({}, discussionId, round, mostIds, leastIds, cb)
expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenCalledWith({ ranks: [], whys: [] })
expect(console.error).toHaveBeenCalledWith('Cannot retrieve whys - user is not logged in.')
})

// Test 2: No ranks or points found
test('Return empty ranks and points if nothing found', async () => {
const cb = jest.fn()
await getWhyRanksAndPoints.call(synuser, discussionId, round, mostIds, leastIds, cb)
expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenCalledWith({ ranks: [], whys: [] })
})

// Test 3: Case where there are 5 why-points for each mostId and leastId
test('Case where there are 5 why-points for each mostId and leastId', async () => {
const cb = jest.fn()

const whys = []
mostIds.concat(leastIds).forEach((parentId, index) => {
for (let i = 0; i < 5; i++) {
whys.push({
_id: new ObjectId(),
parentId: parentId.toString(),
userId,
round,
title: `Why ${parentId}-${i}`, // Unique title
description: `Description ${i}`,
})
}
})

console.log('Inserting whys:', whys)
await Points.insertMany(whys)

const insertedWhys = await Points.find({}).toArray()
console.log('Inserted whys:', insertedWhys)
await getWhyRanksAndPoints.call(synuser, discussionId, round, mostIds, leastIds, cb)

expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenCalledWith({ ranks: [], whys: [] })
})

// Test 4: 5 rankings for each mostId and leastId
test('Case where there are 5 rankings for each mostId and leastId', async () => {
const cb = jest.fn()

const ranks = []
mostIds.concat(leastIds).forEach((parentId, index) => {
for (let i = 0; i < 5; i++) {
ranks.push({
_id: new ObjectId(),
parentId: parentId.toString(),
round,
rank: i,
stage: 'why',
category: 'most',
discussionId,
userId,
})
}
})

const whys = []
mostIds.concat(leastIds).forEach((parentId, index) => {
for (let i = 0; i < 5; i++) {
whys.push({
_id: new ObjectId(),
parentId: parentId.toString(),
userId,
round,
title: `Why ${parentId}-${i}`, // Unique title
description: `Description ${i}`,
})
}
})

await Points.insertMany(whys)

await Ranks.insertMany(ranks)

const insertedRanks = await Ranks.find({}).toArray()
console.log('Inserted ranks:', insertedRanks)
await getWhyRanksAndPoints.call(synuser, discussionId, round, mostIds, leastIds, cb)

expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenCalledWith({ ranks, whys })
})

// Test 5: Rankings for one mostId but not the other or leastId
test('Case where there are 5 rankings for one mostId but not the other or leastId', async () => {
const cb = jest.fn()

const ranks = []

for (let i = 0; i < 5; i++) {
ranks.push({
_id: new ObjectId(),
parentId: mostIds[0].toString(),
round,
rank: i,
stage: 'why',
category: 'most',
discussionId,
userId,
})
}

const whys = []
mostIds.concat(leastIds).forEach((parentId, index) => {
for (let i = 0; i < 5; i++) {
whys.push({
_id: new ObjectId(),
parentId: parentId.toString(),
userId,
round,
title: `Why ${parentId}-${i}`, // Unique title
description: `Description ${i}`,
})
}
})

await Points.insertMany(whys)
await Ranks.insertMany(ranks)

await getWhyRanksAndPoints.call(synuser, discussionId, round, mostIds, leastIds, cb)

expect(cb).toHaveBeenCalledTimes(1)
expect(cb).toHaveBeenCalledWith({ ranks, whys })
})
75 changes: 75 additions & 0 deletions app/socket-apis/get-why-ranks-and-points.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// https://github.com/EnCiv/civil-pursuit/issues/207

const Points = require('../models/points')
const Ranks = require('../models/ranks')
const getRandomWhys = require('./get-random-whys')

const WHY_FETCH_COUNT = 5 // Number of "whys" to fetch when an ID has none

async function getWhyRanksAndPoints(discussionId, round, mostIds, leastIds, cb) {
const cbFailure = errorMsg => {
if (errorMsg) console.error(errorMsg)
if (cb) cb({ ranks: [], whys: [] })
}

if (!this.synuser || !this.synuser.id) {
return cbFailure('Cannot retrieve whys - user is not logged in.')
}

if (!discussionId || typeof round !== 'number' || !Array.isArray(mostIds) || !Array.isArray(leastIds)) {
return cbFailure('Invalid argument provided to getWhyRanksAndPoints.')
}

try {
const userId = this.synuser.id

// Step 1: Fetch ranks
const ranks = await Ranks.find({
discussionId,
round,
userId,
stage: 'why',
}).toArray()

// Step 2: Extract parentIds from ranks
const parentIds = ranks.map(rank => rank.parentId)

// Step 3: Fetch points based on parentIds
const points = await Points.find({ parentId: { $in: parentIds } }).toArray()

// Step 4: Check if all mostIds and leastIds have corresponding why-points
const pointsWithWhys = new Set(points.map(point => point.parentId))

const missingMostIds = mostIds.filter(id => !pointsWithWhys.has(id))
console.log('missingMostIds:', missingMostIds)
const missingLeastIds = leastIds.filter(id => !pointsWithWhys.has(id))
console.log('missingLeastIds:', missingLeastIds)

if (!missingMostIds.length && !missingLeastIds.length) {
console.log('All mostIds and leastIds have corresponding why-points.')
// If all parentIds have corresponding why-points, return ranks and points
return cb({ ranks, whys: points })
}

// Step 5: Fetch missing whys using getRandomWhys
const mostWhysPromises = missingMostIds.map(id => new Promise(resolve => getRandomWhys.call(this, id, 'most', WHY_FETCH_COUNT, resolve)))
const leastWhysPromises = missingLeastIds.map(id => new Promise(resolve => getRandomWhys.call(this, id, 'least', WHY_FETCH_COUNT, resolve)))

const mostWhys = (await Promise.all(mostWhysPromises)).flat()
const leastWhys = (await Promise.all(leastWhysPromises)).flat()

const allWhys = [...mostWhys, ...leastWhys]
console.log('allWhys:', allWhys)
const allWhysIds = allWhys.map(why => why._id)

// Step 6: Fetch points for newly fetched whys
const newWhysPoints = await Points.find({ parentId: { $in: allWhysIds } }).toArray()

// Combine existing and new points
return cb({ ranks, whys: points.concat(newWhysPoints) })
} catch (error) {
return cbFailure('Failed to retrieve ranks and points.')
}
}

module.exports = getWhyRanksAndPoints