diff --git a/.gitignore b/.gitignore index 42a1b3c705..8a0aca8cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,6 @@ link-plugins.sh test.sh .docker/** -!**/.gitkeep \ No newline at end of file +!**/.gitkeep +# stryker temp files +.stryker-tmp diff --git a/.mocharc.yml b/.mocharc.yml index 16d8518d1b..b9246aa609 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -1,4 +1,4 @@ reporter: dot timeout: 25000 exit: true -bail: true +bail: false diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000..9db34b77ab Binary files /dev/null and b/dump.rdb differ diff --git a/package 2.json b/package 2.json new file mode 100644 index 0000000000..de8424d847 --- /dev/null +++ b/package 2.json @@ -0,0 +1,199 @@ +{ + "name": "nodebb", + "license": "GPL-3.0", + "description": "NodeBB Forum", + "version": "3.8.4", + "homepage": "https://www.nodebb.org", + "repository": { + "type": "git", + "url": "https://github.com/NodeBB/NodeBB/" + }, + "main": "app.js", + "scripts": { + "start": "node loader.js", + "lint": "eslint --cache ./nodebb .", + "test": "nyc --reporter=html --reporter=text-summary mocha", + "coverage": "nyc report --reporter=text-lcov > ./coverage/lcov.info", + "coveralls": "nyc report --reporter=text-lcov | coveralls && rm -r coverage" + }, + "nyc": { + "exclude": [ + "src/upgrades/*", + "test/*" + ] + }, + "lint-staged": { + "*.js": [ + "eslint --fix" + ] + }, + "dependencies": { + "@adactive/bootstrap-tagsinput": "0.8.2", + "@fontsource/inter": "5.0.18", + "@fontsource/poppins": "5.0.14", + "@fortawesome/fontawesome-free": "6.5.2", + "@isaacs/ttlcache": "1.4.1", + "@nodebb/spider-detector": "2.0.3", + "@popperjs/core": "2.11.8", + "@socket.io/redis-adapter": "8.3.0", + "ace-builds": "1.33.2", + "archiver": "7.0.1", + "async": "3.2.5", + "autoprefixer": "10.4.19", + "bcryptjs": "2.4.3", + "benchpressjs": "2.5.1", + "body-parser": "1.20.2", + "bootbox": "6.0.0", + "bootstrap": "5.3.3", + "bootswatch": "5.3.3", + "chalk": "4.1.2", + "chart.js": "4.4.2", + "cli-graph": "3.2.2", + "clipboard": "2.0.11", + "colors": "1.4.0", + "commander": "12.0.0", + "compare-versions": "6.1.0", + "compression": "1.7.4", + "connect-flash": "0.1.1", + "connect-mongo": "5.1.0", + "connect-multiparty": "2.2.0", + "connect-pg-simple": "9.0.1", + "connect-redis": "7.1.1", + "cookie-parser": "1.4.6", + "cron": "3.1.7", + "cropperjs": "1.6.2", + "csrf-sync": "4.0.3", + "daemon": "1.1.0", + "diff": "5.2.0", + "esbuild": "0.21.2", + "express": "4.19.2", + "express-session": "1.18.0", + "express-useragent": "1.0.15", + "fetch-cookie": "3.0.1", + "file-loader": "6.2.0", + "fs-extra": "11.2.0", + "graceful-fs": "4.2.11", + "helmet": "7.1.0", + "html-to-text": "9.0.5", + "imagesloaded": "5.0.0", + "ioredis": "5.4.1", + "ipaddr.js": "2.2.0", + "jquery": "3.7.1", + "jquery-deserialize": "2.0.0", + "jquery-form": "4.3.0", + "jquery-serializeobject": "1.0.0", + "jquery-ui": "1.13.3", + "jsesc": "3.0.2", + "json2csv": "5.0.7", + "jsonwebtoken": "9.0.2", + "lodash": "4.17.21", + "logrotate-stream": "0.2.9", + "lru-cache": "10.2.2", + "mime": "3.0.0", + "mkdirp": "3.0.1", + "mongodb": "6.6.1", + "morgan": "1.10.0", + "mousetrap": "1.6.5", + "multiparty": "4.2.3", + "nconf": "0.12.1", + "nodebb-plugin-2factor": "7.5.3", + "nodebb-plugin-composer-default": "10.2.36", + "nodebb-plugin-dbsearch": "6.2.5", + "nodebb-plugin-emoji": "5.1.15", + "nodebb-plugin-emoji-android": "4.0.0", + "nodebb-plugin-location-to-map": "^0.1.1", + "nodebb-plugin-markdown": "12.2.6", + "nodebb-plugin-mentions": "4.4.3", + "nodebb-plugin-ntfy": "1.7.4", + "nodebb-plugin-spam-be-gone": "2.2.2", + "nodebb-rewards-essentials": "1.0.0", + "nodebb-theme-harmony": "1.2.63", + "nodebb-theme-lavender": "7.1.8", + "nodebb-theme-peace": "2.2.6", + "nodebb-theme-persona": "13.3.25", + "nodebb-widget-essentials": "7.0.18", + "nodemailer": "6.9.13", + "nprogress": "0.2.0", + "passport": "0.7.0", + "passport-http-bearer": "1.0.1", + "passport-local": "1.0.0", + "pg": "8.11.5", + "pg-cursor": "2.10.5", + "postcss": "8.4.38", + "postcss-clean": "1.2.0", + "progress-webpack-plugin": "1.0.16", + "prompt": "1.3.0", + "rimraf": "5.0.7", + "rss": "1.2.2", + "rtlcss": "4.1.1", + "sanitize-html": "2.13.0", + "sass": "1.77.1", + "semver": "7.6.2", + "serve-favicon": "2.5.0", + "sharp": "0.32.6", + "sitemap": "7.1.1", + "socket.io": "4.7.5", + "socket.io-client": "4.7.5", + "sortablejs": "1.15.2", + "spdx-license-list": "6.9.0", + "terser-webpack-plugin": "5.3.10", + "textcomplete": "0.18.2", + "textcomplete.contenteditable": "0.1.1", + "timeago": "1.6.7", + "tinycon": "0.6.8", + "toobusy-js": "0.5.1", + "tough-cookie": "4.1.4", + "validator": "13.12.0", + "webpack": "5.91.0", + "webpack-merge": "5.10.0", + "winston": "3.13.0", + "workerpool": "9.1.1", + "xml": "1.0.1", + "xregexp": "5.1.1", + "yargs": "17.7.2", + "zxcvbn": "4.4.2" + }, + "devDependencies": { + "@apidevtools/swagger-parser": "10.1.0", + "@commitlint/cli": "19.3.0", + "@commitlint/config-angular": "19.3.0", + "coveralls": "3.1.1", + "eslint": "8.57.0", + "eslint-config-nodebb": "0.2.1", + "eslint-plugin-import": "2.29.1", + "grunt": "1.6.1", + "grunt-contrib-watch": "1.1.0", + "husky": "8.0.3", + "jsdom": "24.0.0", + "lint-staged": "15.2.2", + "mocha": "10.4.0", + "mocha-lcov-reporter": "1.3.0", + "mockdate": "3.0.5", + "nyc": "15.1.0", + "smtp-server": "3.13.4" + }, + "optionalDependencies": { + "sass-embedded": "1.77.1" + }, + "resolutions": { + "*/jquery": "3.7.1" + }, + "bugs": { + "url": "https://github.com/NodeBB/NodeBB/issues" + }, + "engines": { + "node": ">=18" + }, + "maintainers": [ + { + "name": "Julian Lam", + "email": "julian@nodebb.org", + "url": "https://github.com/julianlam" + }, + { + "name": "Barış Soner Uşaklı", + "email": "baris@nodebb.org", + "url": "https://github.com/barisusakli" + } + ] +} diff --git a/src/cli/index.js b/src/cli/index.js index e6f0485585..7b6886bbff 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -111,7 +111,6 @@ prestart.versionCheck(); if (!configExists && process.argv[2] !== 'setup') { require('./setup').webInstall(); - return; } if (configExists) { diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index c0838b45a0..ed81cce47c 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -1,75 +1,83 @@ 'use strict'; +const helpers = require('./helpers'); + module.exports = function (module) { - const helpers = require('./helpers'); + // Helper Functions + async function executeQuery(client, query) { + return await client.query(query); + } + + async function checkIfKeysExist(module, keys) { + const query = { + name: 'existsArray', + text: ` + SELECT o."_key" k + FROM "legacy_object_live" o + WHERE o."_key" = ANY($1::TEXT[])`, + values: [keys], + }; + console.log("Andrew") + const res = await executeQuery(module.pool, query); + return keys.map(k => res.rows.some(r => r.k === k)); + } + + async function checkIfzSetsExist(module, keys) { + const members = await Promise.all( + keys.map(key => module.getSortedSetRange(key, 0, 0)) + ); + console.log("Andrew") + return members.map(member => member.length > 0); + } + + async function checkExists(module, key) { + console.log("Andrew") + const type = await module.type(key); + if (type === 'zset') { + const members = await module.getSortedSetRange(key, 0, 0); + return members.length > 0; + } + const query = { + name: 'exists', + text: ` + SELECT EXISTS(SELECT * FROM "legacy_object_live" WHERE "_key" = $1::TEXT LIMIT 1) e`, + values: [key], + }; + const res = await executeQuery(module.pool, query); + + return res.rows[0].e; + } + // Module Functions module.flushdb = async function () { - await module.pool.query(`DROP SCHEMA "public" CASCADE`); - await module.pool.query(`CREATE SCHEMA "public"`); + await executeQuery(module.pool, { text: `DROP SCHEMA "public" CASCADE` }); + await executeQuery(module.pool, { text: `CREATE SCHEMA "public"` }); }; module.emptydb = async function () { - await module.pool.query(`DELETE FROM "legacy_object"`); + await executeQuery(module.pool, { text: `DELETE FROM "legacy_object"` }); }; module.exists = async function (key) { - if (!key) { - return; - } - const isArray = Array.isArray(key); - if (isArray && !key.length) { - return []; - } + if (!key) return; - async function checkIfzSetsExist(keys) { - const members = await Promise.all( - keys.map(key => module.getSortedSetRange(key, 0, 0)) - ); - return members.map(member => member.length > 0); - } - - async function checkIfKeysExist(keys) { - const res = await module.pool.query({ - name: 'existsArray', - text: ` - SELECT o."_key" k - FROM "legacy_object_live" o - WHERE o."_key" = ANY($1::TEXT[])`, - values: [keys], - }); - return keys.map(k => res.rows.some(r => r.k === k)); - } + const isArray = Array.isArray(key); + if (isArray && !key.length) return []; - // Redis/Mongo consider empty zsets as non-existent, match that behaviour if (isArray) { const types = await Promise.all(key.map(module.type)); - const zsetKeys = key.filter((_key, i) => types[i] === 'zset'); - const otherKeys = key.filter((_key, i) => types[i] !== 'zset'); - const [zsetExits, otherExists] = await Promise.all([ - checkIfzSetsExist(zsetKeys), - checkIfKeysExist(otherKeys), + const zsetKeys = key.filter((_, i) => types[i] === 'zset'); + const otherKeys = key.filter((_, i) => types[i] !== 'zset'); + const [zsetExists, otherExists] = await Promise.all([ + checkIfzSetsExist(module, zsetKeys), + checkIfKeysExist(module, otherKeys), ]); - const existsMap = Object.create(null); - zsetKeys.forEach((k, i) => { existsMap[k] = zsetExits[i]; }); + const existsMap = {}; + zsetKeys.forEach((k, i) => { existsMap[k] = zsetExists[i]; }); otherKeys.forEach((k, i) => { existsMap[k] = otherExists[i]; }); return key.map(k => existsMap[k]); } - const type = await module.type(key); - if (type === 'zset') { - const members = await module.getSortedSetRange(key, 0, 0); - return members.length > 0; - } - const res = await module.pool.query({ - name: 'exists', - text: ` - SELECT EXISTS(SELECT * - FROM "legacy_object_live" - WHERE "_key" = $1::TEXT - LIMIT 1) e`, - values: [key], - }); - - return res.rows[0].e; + return await checkExists(module, key); }; module.scan = async function (params) { diff --git a/stryker.config.json b/stryker.config.json new file mode 100644 index 0000000000..40229ad48f --- /dev/null +++ b/stryker.config.json @@ -0,0 +1,13 @@ +{ + "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "_comment": "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.", + "packageManager": "npm", + "reporters": [ + "html", + "clear-text", + "progress" + ], + "testRunner": "command", + "testRunner_comment": "Take a look at (missing 'homepage' URL in package.json) for information about the command plugin.", + "coverageAnalysis": "off" +} \ No newline at end of file diff --git a/test/database/keys.js b/test/database/keys.js index 984a5e7a66..c015fd5722 100644 --- a/test/database/keys.js +++ b/test/database/keys.js @@ -55,6 +55,16 @@ describe('Key methods', () => { }); }); + it('should check for existence of both regular keys and zset keys', async () => { + await db.set('testRegularKey', 'value1'); + await db.sortedSetAdd('testZsetKey', 1, 'member1'); + const result = await db.exists(['testRegularKey', 'testZsetKey']); + assert.deepStrictEqual(result, [true, true]); + const nonExistingResult = await db.exists(['testRegularKey', 'nonExistingKey']); + assert.deepStrictEqual(nonExistingResult, [true, false]); + }); + + it('should return false if key does not exist', (done) => { db.exists('doesnotexist', function (err, exists) { assert.ifError(err);