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

finished validation issue number:240 #281

Open
wants to merge 5 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
32 changes: 10 additions & 22 deletions app/dturn/__tests__/rank-statements.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
// https://github.com/EnCiv/civil-pursuit/issues/154
const {
initDiscussion,
insertStatementId,
getStatementIds,
putGroupings,
getUserRecord,
rankMostImportant,
Discussions,
} = require('../dturn')
const { initDiscussion, insertStatementId, getStatementIds, putGroupings, getUserRecord, rankMostImportant, Discussions } = require('../dturn')

const DISCUSSION_ID = 'testRankingDiscussion'
const USER_ID = 'user1'
Expand All @@ -24,37 +16,33 @@ const OPTIONS = {
describe('Test ranking scenarios', () => {
beforeAll(async () => {
await initDiscussion(DISCUSSION_ID, OPTIONS)
const props = []
for (let i = 0; i < 20; i++) {
props.push([DISCUSSION_ID, `user${i}`, `statement${i}`])

const totalStatements = OPTIONS.group_size * 2 - 1
const insertPromises = []
for (let i = 0; i < totalStatements; i++) {
insertPromises.push(insertStatementId(DISCUSSION_ID, `user${i}`, `statement${i}`))
}
for await (const args of props) {
await insertStatementId(...args)
await Promise.all(insertPromises)

if (!Discussions[DISCUSSION_ID].Uitems[USER_ID]) {
await insertStatementId(DISCUSSION_ID, USER_ID, `statement-${USER_ID}`)
}
})

test('Can rank 2 statements as most important', async () => {
const statements = await getStatementIds(DISCUSSION_ID, 0, USER_ID)

// Ensure statements is defined before proceeding
expect(statements).toBeDefined()
expect(statements.length).toBe(10)

const statement1 = statements[0]
const statement2 = statements[1]

// Rank both statements as most important
await rankMostImportant(DISCUSSION_ID, 0, USER_ID, statement1, 1)
await rankMostImportant(DISCUSSION_ID, 0, USER_ID, statement2, 1)

const userRecord = getUserRecord(DISCUSSION_ID, USER_ID)

// Check that the rankings were applied correctly
expect(userRecord[0].shownStatementIds[statement1].rank).toBe(1)
expect(userRecord[0].shownStatementIds[statement2].rank).toBe(1)
})

afterAll(() => {
delete Discussions[DISCUSSION_ID] // Clean up the discussion
})
})
277 changes: 157 additions & 120 deletions app/dturn/dturn.js

Large diffs are not rendered by default.

179 changes: 148 additions & 31 deletions app/dturn/tests/dturn.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,159 @@
// https://github.com/EnCiv/civil-pursuit/issues/171

const { initDiscussion } = require('../dturn')
const { insertStatementId, getStatementIds, putGroupings, rankMostImportant, initDiscussion, Discussions } = require('../dturn')

import { Mongo } from '@enciv/mongo-collections'
import { MongoMemoryServer } from 'mongodb-memory-server'
describe('dturn.js functions', () => {
// Reset Discussions before each test to ensure a clean state
beforeEach(async () => {
Object.keys(Discussions).forEach(key => delete Discussions[key])

// Config
const DISCUSSION_ID = 1
const nonexistentOptionErrorRegex = /'a_nonexistent_option' is not an option for initDiscussion()/
await initDiscussion('testDiscussion', {
updates: jest.fn(), // Mock updates function
updateUInfo: jest.fn(), // Mock updateUInfo function
})
})

let MemoryServer
// Testing the insertStatementId function
describe('insertStatementId', () => {
test('should return undefined if the discussion does not exist', async () => {
// If the discussionId does not exist, insertStatementId should return undefined
const result = await insertStatementId('invalid-discussion', 'user1', 'stmt1')
expect(result).toBeUndefined()
})

beforeAll(async () => {
MemoryServer = await MongoMemoryServer.create()
const uri = MemoryServer.getUri()
await Mongo.connect(uri)
})
test('should allow new users to join by adding a statement', async () => {
// Ensure a user can join by submitting a statement, even if they are new
const discussionId = 'test-discussion'
const userId = 'newUser'
const statementId = 'stmt1'

afterAll(async () => {
Mongo.disconnect()
MemoryServer.stop()
})
Discussions[discussionId] = {
ShownStatements: { 0: [] }, // Ensure round 0 exists
Uitems: {}, // No userId exists yet
updates: jest.fn(),
updateUInfo: jest.fn(),
}

// Tests
test('Succeed if all options are valid.', async () => {
initDiscussion(DISCUSSION_ID, { group_size: 15 })
})
const result = await insertStatementId(discussionId, userId, statementId)

test('Fail if all options are not valid.', async () => {
const func = async () => {
await initDiscussion(DISCUSSION_ID, { a_nonexistent_option: 12345 })
}
expect(func()).rejects.toThrow(nonexistentOptionErrorRegex)
})
// Validate that the statement has been successfully inserted
expect(result).toBe(statementId)
expect(Discussions[discussionId].Uitems[userId]).toBeDefined() // User should be created
expect(Discussions[discussionId].ShownStatements[0]).toContainEqual({ statementId, shownCount: 0, rank: 0 })
})
})

// Testing the getStatementIds function
describe('getStatementIds', () => {
test('should return statement IDs for a user in a discussion', async () => {
// Ensures that a user can receive a list of statement IDs they are assigned to evaluate
const discussionId = 'test-discussion'
const userId = 'user1'

// Setting up the discussion with a few statements and the user’s assigned statements
Discussions[discussionId] = {
ShownStatements: [
[
{ statementId: 'stmt1', shownCount: 0, rank: 0 },
{ statementId: 'stmt2', shownCount: 0, rank: 0 },
{ statementId: 'stmt3', shownCount: 0, rank: 0 },
],
],
Uitems: { [userId]: { 0: { shownStatementIds: { stmt1: {}, stmt2: {}, stmt3: {} } } } },
group_size: 2, // Set group size
updateUInfo: jest.fn(),
}

const result = await getStatementIds(discussionId, 0, userId)
expect(result).toBeDefined()
expect(Array.isArray(result)).toBe(true)
expect(result.length).toBeGreaterThanOrEqual(2)
expect(result).toContain('stmt1')
expect(result).toContain('stmt2')
})

test('should return undefined if the user is not part of the discussion', async () => {
// If the user is not part of the discussion, they should not receive statement IDs
const discussionId = 'test-discussion'
Discussions[discussionId] = {
ShownStatements: [[{ statementId: 'stmt1', shownCount: 0, rank: 0 }]],
Uitems: {}, // No users exist
}

const result = await getStatementIds(discussionId, 0, 'nonexistentUser')
expect(result).toBeUndefined()
})
})

// Testing the putGroupings function
describe('putGroupings', () => {
test('should throw an error if statement IDs were not shown to the user', async () => {
// Ensures that a user cannot group statements they have not seen
const discussionId = 'test-discussion'
const userId = 'user1'

Discussions[discussionId] = {
Uitems: { [userId]: { 0: { shownStatementIds: { stmt1: {} } } } },
updateUInfo: jest.fn(),
}

await expect(putGroupings(discussionId, 0, userId, [['stmt2']])).rejects.toThrow('Statement stmt2 was not shown to user user1 in round 0')
})
})

// Testing the rankMostImportant function
describe('rankMostImportant', () => {
test('should return undefined if the discussion is not initialized', async () => {
// If the discussion does not exist, ranking a statement should return undefined
const result = await rankMostImportant('invalid-discussion', 0, 'user1', 'stmt1', 1)
expect(result).toBeUndefined()
})

test('should return undefined if the user has not participated in the round', async () => {
// Ensures that if a user has not engaged in a round, they cannot rank statements
const discussionId = 'test-discussion'
const userId = 'user1'

Discussions[discussionId] = { Uitems: {}, updateUInfo: jest.fn() }

const result = await rankMostImportant(discussionId, 0, userId, 'stmt1', 1)
expect(result).toBeUndefined()
})

test('should correctly update ranking for a shown statement', async () => {
// Ensures that ranking a statement correctly updates its rank value
const discussionId = 'test-discussion'
const userId = 'user1'
const statementId = 'stmt1'

// Properly initializing Discussions structure
Discussions[discussionId] = {
ShownStatements: [[{ statementId: 'stmt1', shownCount: 0, rank: 0 }]], // Ensure statementId exists
Uitems: {
[userId]: {
0: {
shownStatementIds: { [statementId]: { rank: 0 } },
},
},
},
updateUInfo: jest.fn(async update => {
Object.keys(update).forEach(uid => {
Object.keys(update[uid]).forEach(did => {
Object.keys(update[uid][did]).forEach(round => {
Object.keys(update[uid][did][round].shownStatementIds).forEach(stmt => {
Discussions[did].Uitems[uid][round].shownStatementIds[stmt].rank = update[uid][did][round].shownStatementIds[stmt].rank
})
})
})
})
}),
}

await rankMostImportant(discussionId, 0, userId, statementId, 1)

test('Fail if at least 1 option is not valid.', async () => {
const func = async () => {
await initDiscussion(DISCUSSION_ID, { group_size: 10, a_nonexistent_option: 12345 })
}
expect(func()).rejects.toThrow(nonexistentOptionErrorRegex)
// Validate that ranking was successfully applied
expect(Discussions[discussionId].Uitems[userId][0]).toBeDefined()
expect(Discussions[discussionId].Uitems[userId][0].shownStatementIds[statementId].rank).toBe(1)
})
})
})
109 changes: 62 additions & 47 deletions app/socket-apis/__tests__/complete-round.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// https://github.com/EnCiv/civil-pursuit/issues/212
const dturn = require('../../dturn/dturn')
const completeRound = require('../complete-round')
const { Discussions, initDiscussion } = require('../../dturn/dturn')
const expect = require('expect')
const { insertStatementId, getStatementIds } = dturn

const userId = '7b4c3a5e8d1f2b9c'
Expand Down Expand Up @@ -48,66 +50,79 @@ test('Return undefined if discussion is not loaded (getStatementIds fails).', as
expect(cb).toHaveBeenCalledWith(undefined)

// Verify console.error calls and their messages
expect(console.error).toHaveBeenCalledTimes(2)
expect(console.error).toHaveBeenNthCalledWith(1, `No ShownStatements found for discussion ${discussionId}`)
expect(console.error).toHaveBeenNthCalledWith(2, 'No statements found to rank.')
// Now we do not need to console anymore
// expect(console.error).toHaveBeenCalledTimes(2)
// expect(console.error).toHaveBeenNthCalledWith(1, `No ShownStatements found for discussion ${discussionId}`)
// expect(console.error).toHaveBeenNthCalledWith(2, 'No statements found to rank.')
})

// Test 3: Success case
test('Success: Insert statements and rank them.', async () => {
await dturn.initDiscussion(discussionId, {
group_size: 10,
updateUInfo: obj => {
UInfoHistory.push(obj)
},
describe('Complete Round API Tests', () => {
beforeEach(async () => {
await initDiscussion(discussionId, {
group_size: 10,
updateUInfo: obj => {
UInfoHistory.push(obj)
},
})
})
const cb = jest.fn()

// Insert statements
for (let i = 1; i <= 20; i++) {
const statementId = `5f${i.toString(16).padStart(14, '0')}`
const userId = `6e${i.toString(16).padStart(14, '0')}`
await insertStatementId(discussionId, userId, statementId)
}

// Get statement IDs
const userIdForGetStatementIds = 'user1'
const statementIds = await getStatementIds(discussionId, 0, userIdForGetStatementIds)

// Rank the statements
const idRanks = [{ [statementIds[1]]: 1 }, { [statementIds[2]]: 1 }]
await completeRound.call(synuser, discussionId, 0, idRanks, cb)

// Verify that callback function cb was called correctly
expect(cb).toHaveBeenCalledWith(true)

const expectedEntries = [
{
[userId]: {
[discussionId]: {
0: {
shownStatementIds: {
[statementIds[1]]: { rank: 1 },
test('Success: Insert statements and rank them.', async () => {
const cb = jest.fn()

// Insert statements
for (let i = 1; i <= 20; i++) {
const statementId = `5f${i.toString(16).padStart(14, '0')}`
const userId = `6e${i.toString(16).padStart(14, '0')}`
await insertStatementId(discussionId, userId, statementId)
}

// Get statement IDs
const userIdForGetStatementIds = 'user1'
const statementIds = await getStatementIds(discussionId, 0, userIdForGetStatementIds)

console.log(`DEBUG: statementIds received:`, statementIds)

if (!statementIds || statementIds.length < 3) {
console.error(`ERROR: Insufficient statementIds: ${statementIds ? statementIds.length : 'undefined'}`)
return
}

// Rank the statements
const idRanks = [{ [statementIds[1]]: 1 }, { [statementIds[2]]: 1 }]
await completeRound.call(synuser, discussionId, 0, idRanks, cb)

// Verify that callback function cb was called correctly
expect(cb).toHaveBeenCalledWith(true)

const expectedEntries = [
{
[userId]: {
[discussionId]: {
0: {
shownStatementIds: {
[statementIds[1]]: { rank: 1 },
},
},
},
},
},
},
{
[userId]: {
[discussionId]: {
0: {
shownStatementIds: {
[statementIds[2]]: { rank: 1 },
{
[userId]: {
[discussionId]: {
0: {
shownStatementIds: {
[statementIds[2]]: { rank: 1 },
},
},
},
},
},
},
]
]

// Check if UInfoHistory contains each expected entry
expectedEntries.forEach(expectedEntry => {
expect(UInfoHistory).toContainEqual(expectedEntry)
// Check if UInfoHistory contains each expected entry
expectedEntries.forEach(expectedEntry => {
expect(UInfoHistory).toContainEqual(expectedEntry)
})
})
})
Loading