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

Get user post ranks and top ranked whys#208 #263

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions app/socket-apis/__tests__/get-user-post-ranks-and-top-ranked-whys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// https://github.com/EnCiv/civil-pursuit/issues/208

import { MongoMemoryServer } from 'mongodb-memory-server';
import { Mongo } from '@enciv/mongo-collections';
import { ObjectId } from 'mongodb';
import Rankings from '../../models/rankings';
import Points from '../../models/points';
import getUserPostRanksAndTopRankedWhys from '../get-user-post-ranks-and-top-ranked-whys';

let memoryServer;
let db;

const synuser = { synuser: { id: new ObjectId().toString() } };

beforeAll(async () => {
memoryServer = await MongoMemoryServer.create();
const uri = memoryServer.getUri();
await Mongo.connect(uri);
db = Mongo.db;
Rankings.setCollectionProps();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling .setCollectionProps is not needed for these models. It's done when the model is loaded.

Points.setCollectionProps();
});

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

beforeEach(async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
});

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

test('User not logged in', async () => {
const callback = jest.fn();
await getUserPostRanksAndTopRankedWhys.call({}, 'discussion1', 1, ['id1', 'id2'], callback);
expect(callback).toHaveBeenCalledWith(undefined);
expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/no user logged in/));
});

test('10 points in ids, nothing ranked', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of making new ids here, every time the test is run, generate 10 (use console.info) and then copy them into an array here.

I'm learning that in the expect statements, what's expected needs to be more visible, meaning .objectToMatch({...}) so that when we look at the code in the test we can see what's expected. -- We are missing things by not looking at it.

const ids = Array.from({ length: 10 }, () => new ObjectId().toString());

const callback = jest.fn();
await getUserPostRanksAndTopRankedWhys.call(synuser, 'discussion1', 1, ids, callback);

expect(callback).toHaveBeenCalledWith({ ranks: [], whys: [] });
});

test('5 have 2 why mosts, 2 have why leasts, 1 has 1 why most, 1 has 1 why least – nothing ranked', async () => {
const ids = Array.from({ length: 10 }, () => new ObjectId().toString());

await db.collection('points').insertMany([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this works, but it would be shorter and clearer to say Points.insertMany

...ids.slice(0, 5).flatMap(id => [
{ _id: new ObjectId(), parentId: id, category: 'most', title: `Why Most 1 for ${id}`, userId: '123456' },
{ _id: new ObjectId(), parentId: id, category: 'most', title: `Why Most 2 for ${id}`, userId: '123456' },
]),
...ids.slice(5, 7).flatMap(id => [
{ _id: new ObjectId(), parentId: id, category: 'least', title: `Why Least 1 for ${id}`, userId: '123456' },
{ _id: new ObjectId(), parentId: id, category: 'least', title: `Why Least 2 for ${id}`, userId: '123456' },
]),
{ _id: new ObjectId(), parentId: ids[7], category: 'most', title: `Why Most for ${ids[7]}`, userId: '123456' },
{ _id: new ObjectId(), parentId: ids[8], category: 'least', title: `Why Least for ${ids[8]}`, userId: '123456' },
]);

const callback = jest.fn();
await getUserPostRanksAndTopRankedWhys.call(synuser, 'discussion1', 1, ids, callback);

expect(callback).toHaveBeenCalledWith({
ranks: [],
whys: expect.any(Array),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of .any use expect(callback.mock.calls[0][0]).toMatchObject([]) and it will through the error and then the error message text can be used to create the object to expect.

But this will only work if the ids are the same every time.

});
});

test('Above scenario with 5 ranked, nothing for other 5', async () => {
const ids = Array.from({ length: 10 }, () => new ObjectId().toString());

const rankings = ids.slice(0, 5).map(id => ({
_id: new ObjectId(),
parentId: id,
category: 'most',
stage: 'post',
discussionId: 'discussion1',
round: 1,
userId: '123456',
}));

if (rankings.length > 0) {
await db.collection('rankings').insertMany(rankings);
}

const callback = jest.fn();
await getUserPostRanksAndTopRankedWhys.call(synuser, 'discussion1', 1, ids, callback);

expect(callback).toHaveBeenCalledWith({
ranks: expect.any(Array),
whys: [],
});
});

test('Above scenario with all ranked', async () => {
const ids = Array.from({ length: 10 }, () => new ObjectId().toString());

const rankings = ids.map(id => ({
_id: new ObjectId(),
parentId: id,
category: 'most',
stage: 'post',
discussionId: 'discussion1',
round: 1,
userId: '123456',
}));

if (rankings.length > 0) {
await db.collection('rankings').insertMany(rankings);
}

const callback = jest.fn();
await getUserPostRanksAndTopRankedWhys.call(synuser, 'discussion1', 1, ids, callback);

expect(callback).toHaveBeenCalledWith({
ranks: expect.any(Array),
whys: expect.any(Array),
});
});
1 change: 1 addition & 0 deletions app/socket-apis/get-top-ranked-whys-for-point.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// https://github.com/EnCiv/civil-pursuit/issues/136
const Points = require('../models/points')
const Rankings = require('../models/rankings')
const { ObjectId } = require('mongodb')
Expand Down
61 changes: 61 additions & 0 deletions app/socket-apis/get-user-post-ranks-and-top-ranked-whys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// https://github.com/EnCiv/civil-pursuit/issues/208
const Rankings = require('../models/rankings');
const getTopRankedWhysForPoint = require('./get-top-ranked-whys-for-point');
const { ObjectId } = require('mongodb');

async function getUserPostRanksAndTopRankedWhys(discussionId, round, ids, cb) {
if (!this.synuser || !this.synuser.id) {
console.error('getUserPostRanksAndTopRankedWhys called but no user logged in');
return cb && cb(undefined);
}

if (!discussionId || typeof round !== 'number' || !Array.isArray(ids) || ids.length === 0) {
console.error('getUserPostRanksAndTopRankedWhys called with invalid parameters');
return cb && cb(undefined);
}

try {
// Fetch rankings for discussionId, round, and stage 'post'
const ranks = await Rankings.aggregate([
{
$match: {
discussionId: discussionId.toString(),
round,
stage: 'post',
parentId: { $in: ids.map(id => id.toString()) },
},
},
]).toArray();

// Remove userId for ranks not created by the current user
const filteredRanks = ranks.map(({ userId, ...rest }) =>
userId === this.synuser.id ? { userId, ...rest } : rest
);

// Fetch top-ranked whys for each point in ids
const topWhys = await Promise.all(
ids.map(async id => {
return new Promise(resolve =>
getTopRankedWhysForPoint.call(
this,
id.toString(),
'most',
0,
1,
whys => resolve(whys[0] || null) // Get the top-ranked why point
)
);
})
);

// Filter out nulls from topWhys
const filteredWhys = topWhys.filter(Boolean);

cb({ ranks: filteredRanks, whys: filteredWhys });
} catch (error) {
console.error('Error in getUserPostRanksAndTopRankedWhys:', error);
cb(undefined);
}
}

module.exports = getUserPostRanksAndTopRankedWhys;