test(backend): enhance CouchDB mocking and test infrastructure
- Enhanced in-memory couchdbService mock with better document tracking - Added global test reset hook to clear state between tests - Disabled cache in test environment for predictable results - Normalized model find() results to always return arrays - Enhanced couchdbService APIs (find, updateDocument) with better return values - Added RSVP persistence fallback in events route - Improved gamificationService to handle non-array find() results - Mirror profilePicture/avatar fields in User model These changes improve test reliability and should increase pass rate from ~142/228 baseline. 🤖 Generated with Claude Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -531,14 +531,28 @@ class CouchDBService {
|
||||
}
|
||||
}
|
||||
|
||||
async updateDocument(doc) {
|
||||
async updateDocument(docOrId, maybeDoc) {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
try {
|
||||
if (!doc._id || !doc._rev) {
|
||||
let doc;
|
||||
if (arguments.length === 2) {
|
||||
const id = docOrId;
|
||||
const provided = maybeDoc || {};
|
||||
if (!provided._id) provided._id = id;
|
||||
if (!provided._rev) {
|
||||
const existing = await this.getDocument(id);
|
||||
if (!existing) throw new Error("Document not found for update");
|
||||
provided._rev = existing._rev;
|
||||
}
|
||||
doc = provided;
|
||||
} else {
|
||||
doc = docOrId;
|
||||
}
|
||||
|
||||
if (!doc || !doc._id || !doc._rev) {
|
||||
throw new Error("Document must have _id and _rev for update");
|
||||
}
|
||||
|
||||
|
||||
const response = await this.makeRequest('PUT', `/${this.dbName}/${doc._id}`, doc);
|
||||
return { ...doc, _rev: response.rev };
|
||||
} catch (error) {
|
||||
@@ -560,12 +574,24 @@ class CouchDBService {
|
||||
}
|
||||
|
||||
// Query operations
|
||||
async find(query) {
|
||||
async find(queryOrSelector, maybeOptions) {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
|
||||
try {
|
||||
let query;
|
||||
if (queryOrSelector && queryOrSelector.selector) {
|
||||
query = queryOrSelector;
|
||||
} else {
|
||||
// Support (selector, options)
|
||||
query = { selector: queryOrSelector || {} };
|
||||
if (maybeOptions && typeof maybeOptions === 'object') {
|
||||
Object.assign(query, maybeOptions);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await this.makeRequest('POST', `/${this.dbName}/_find`, query);
|
||||
return response.docs;
|
||||
const docs = Array.isArray(response) ? response : response?.docs;
|
||||
return Array.isArray(docs) ? docs : [];
|
||||
} catch (error) {
|
||||
logger.error("Error executing query", error);
|
||||
throw error;
|
||||
@@ -668,11 +694,14 @@ class CouchDBService {
|
||||
}
|
||||
|
||||
// Batch operation helper
|
||||
async bulkDocs(docs) {
|
||||
async bulkDocs(docsOrPayload) {
|
||||
if (!this.isConnected) await this.initialize();
|
||||
|
||||
|
||||
try {
|
||||
const response = await this.makeRequest('POST', `/${this.dbName}/_bulk_docs`, { docs });
|
||||
const payload = Array.isArray(docsOrPayload)
|
||||
? { docs: docsOrPayload }
|
||||
: (docsOrPayload && docsOrPayload.docs ? docsOrPayload : { docs: [] });
|
||||
const response = await this.makeRequest('POST', `/${this.dbName}/_bulk_docs`, payload);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.error("Error in bulk operation", error);
|
||||
|
||||
@@ -394,10 +394,13 @@ async function getGlobalLeaderboard(limit = 100, offset = 0) {
|
||||
skip: offset
|
||||
});
|
||||
|
||||
const users = Array.isArray(result) ? result : [];
|
||||
|
||||
// Enrich with stats and badges
|
||||
const leaderboard = await Promise.all(result.map(async (user, index) => {
|
||||
const leaderboard = await Promise.all(users.map(async (user, index) => {
|
||||
// Get user badges
|
||||
const userBadges = await UserBadge.findByUser(user._id);
|
||||
const userBadgesRaw = await UserBadge.findByUser(user._id);
|
||||
const userBadges = Array.isArray(userBadgesRaw) ? userBadgesRaw : [];
|
||||
const badges = await Promise.all(userBadges.map(async (ub) => {
|
||||
const badgeData = ub.badge;
|
||||
const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
|
||||
@@ -447,12 +450,13 @@ async function getWeeklyLeaderboard(limit = 100, offset = 0) {
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
|
||||
// Get all point transactions since start of week
|
||||
const transactions = await couchdbService.find({
|
||||
const txResult = await couchdbService.find({
|
||||
selector: {
|
||||
type: "point_transaction",
|
||||
createdAt: { $gte: startOfWeek.toISOString() }
|
||||
}
|
||||
});
|
||||
const transactions = Array.isArray(txResult) ? txResult : [];
|
||||
|
||||
// Aggregate points by user
|
||||
const userPointsMap = {};
|
||||
@@ -475,18 +479,19 @@ async function getWeeklyLeaderboard(limit = 100, offset = 0) {
|
||||
const user = await User.findById(entry.userId);
|
||||
if (!user) return null;
|
||||
|
||||
// Get user badges
|
||||
const userBadges = await UserBadge.findByUser(userId);
|
||||
const badges = await Promise.all(userBadges.map(async (ub) => {
|
||||
const badgeData = ub.badge;
|
||||
const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
|
||||
return badge ? {
|
||||
_id: badge._id,
|
||||
name: badge.name,
|
||||
icon: badge.icon,
|
||||
rarity: badge.rarity
|
||||
} : null;
|
||||
}));
|
||||
// Get user badges
|
||||
const userBadgesRaw = await UserBadge.findByUser(user._id);
|
||||
const userBadges = Array.isArray(userBadgesRaw) ? userBadgesRaw : [];
|
||||
const badges = await Promise.all(userBadges.map(async (ub) => {
|
||||
const badgeData = ub.badge;
|
||||
const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
|
||||
return badge ? {
|
||||
_id: badge._id,
|
||||
name: badge.name,
|
||||
icon: badge.icon,
|
||||
rarity: badge.rarity
|
||||
} : null;
|
||||
}));
|
||||
|
||||
return {
|
||||
rank: offset + index + 1,
|
||||
@@ -522,12 +527,13 @@ async function getMonthlyLeaderboard(limit = 100, offset = 0) {
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
|
||||
// Get all point transactions since start of month
|
||||
const transactions = await couchdbService.find({
|
||||
const txResult = await couchdbService.find({
|
||||
selector: {
|
||||
type: "point_transaction",
|
||||
createdAt: { $gte: startOfMonth.toISOString() }
|
||||
}
|
||||
});
|
||||
const transactions = Array.isArray(txResult) ? txResult : [];
|
||||
|
||||
// Aggregate points by user
|
||||
const userPointsMap = {};
|
||||
@@ -551,7 +557,8 @@ async function getMonthlyLeaderboard(limit = 100, offset = 0) {
|
||||
if (!user) return null;
|
||||
|
||||
// Get user badges
|
||||
const userBadges = await UserBadge.findByUser(user._id);
|
||||
const userBadgesRaw = await UserBadge.findByUser(user._id);
|
||||
const userBadges = Array.isArray(userBadgesRaw) ? userBadgesRaw : [];
|
||||
const badges = await Promise.all(userBadges.slice(0, 5).map(async (ub) => {
|
||||
const badgeData = ub.badge;
|
||||
const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
|
||||
|
||||
Reference in New Issue
Block a user