mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-24 09:20:32 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
129474f268 | ||
|
|
40170f8133 | ||
|
|
d4452ba25c | ||
|
|
5c6a631fc6 | ||
|
|
dc1cd3feaa | ||
|
|
841b856e27 | ||
|
|
abf81ec57d | ||
|
|
b89502ce0b | ||
|
|
381e64e657 | ||
|
|
110b867ed5 | ||
|
|
4b006b37cc | ||
|
|
e51c8de9cb | ||
|
|
918008fffd | ||
|
|
2e2b1e9d6c | ||
|
|
ca42094f3f | ||
|
|
3d07f14859 | ||
|
|
32175666d5 | ||
|
|
1501a9303c | ||
|
|
db75571923 |
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "3.6.0",
|
||||
"version": "3.5.3",
|
||||
"homepage": "https://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,6 +38,8 @@
|
||||
"archiver": "6.0.1",
|
||||
"async": "3.2.5",
|
||||
"autoprefixer": "10.4.16",
|
||||
"axios": "1.6.2",
|
||||
"axios-cookiejar-support": "4.0.7",
|
||||
"bcryptjs": "2.4.3",
|
||||
"benchpressjs": "2.5.1",
|
||||
"body-parser": "1.20.2",
|
||||
@@ -67,8 +69,8 @@
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.17.3",
|
||||
"express-useragent": "1.0.15",
|
||||
"fetch-cookie": "2.1.0",
|
||||
"file-loader": "6.2.0",
|
||||
"form-data": "4.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"graceful-fs": "4.2.11",
|
||||
"helmet": "7.1.0",
|
||||
@@ -103,7 +105,7 @@
|
||||
"nodebb-plugin-ntfy": "1.7.3",
|
||||
"nodebb-plugin-spam-be-gone": "2.2.0",
|
||||
"nodebb-rewards-essentials": "1.0.0",
|
||||
"nodebb-theme-harmony": "1.1.105",
|
||||
"nodebb-theme-harmony": "1.1.103",
|
||||
"nodebb-theme-lavender": "7.1.5",
|
||||
"nodebb-theme-peace": "2.1.25",
|
||||
"nodebb-theme-persona": "13.2.49",
|
||||
@@ -127,7 +129,7 @@
|
||||
"sass": "1.69.5",
|
||||
"semver": "7.5.4",
|
||||
"serve-favicon": "2.5.0",
|
||||
"sharp": "0.33.1",
|
||||
"sharp": "0.33.0",
|
||||
"sitemap": "7.1.1",
|
||||
"socket.io": "4.7.2",
|
||||
"socket.io-client": "4.7.2",
|
||||
@@ -146,7 +148,7 @@
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "5.10.0",
|
||||
"winston": "3.11.0",
|
||||
"workerpool": "9.0.1",
|
||||
"workerpool": "8.0.0",
|
||||
"xml": "1.0.1",
|
||||
"xregexp": "5.1.1",
|
||||
"yargs": "17.7.2",
|
||||
@@ -157,9 +159,9 @@
|
||||
"@commitlint/cli": "18.4.3",
|
||||
"@commitlint/config-angular": "18.4.3",
|
||||
"coveralls": "3.1.1",
|
||||
"eslint": "8.56.0",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-config-nodebb": "0.2.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"grunt": "1.6.1",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"husky": "8.0.3",
|
||||
@@ -181,7 +183,7 @@
|
||||
"url": "https://github.com/NodeBB/NodeBB/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=16"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
@@ -195,4 +197,4 @@
|
||||
"url": "https://github.com/barisusakli"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Internal Server Error</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
let count = 0;
|
||||
const bounce = document.getElementById('click-me');
|
||||
bounce.onclick = function() {
|
||||
count++;
|
||||
bounce.className = '';
|
||||
setTimeout(function() {
|
||||
bounce.className = 'animated bounce';
|
||||
}, 50);
|
||||
|
||||
if (count > 5) {
|
||||
document.getElementById('hide').className = '';
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="center">
|
||||
<h1 id="click-me" class="animated bounce">500</h1>
|
||||
<p>
|
||||
<strong>Internal server error. </strong>
|
||||
</p>
|
||||
<p>
|
||||
{message}
|
||||
</p>
|
||||
<p>
|
||||
<small id="hide" class="hide">Alright. You can stop clicking... it's not going to make the site come back sooner!</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
141
public/503.html
141
public/503.html
@@ -2,12 +2,147 @@
|
||||
<head>
|
||||
<title>Excessive Load Warning</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
background: #00A9EA;
|
||||
color: white;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
text-align: center;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 250px;
|
||||
color: #fff;
|
||||
opacity: 0.5;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p strong {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1 {
|
||||
font-size: 125px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p strong {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
-webkit-transform: translateY(50%);
|
||||
-ms-transform: translateY(50%);
|
||||
transform: translateY(50%);
|
||||
}
|
||||
|
||||
@-webkit-keyframes bounce {
|
||||
0%, 20%, 53%, 80%, 100% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
|
||||
40%, 43% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -30px, 0);
|
||||
transform: translate3d(0, -30px, 0);
|
||||
}
|
||||
|
||||
70% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -15px, 0);
|
||||
transform: translate3d(0, -15px, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
-webkit-transform: translate3d(0,-4px,0);
|
||||
transform: translate3d(0,-4px,0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 53%, 80%, 100% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
|
||||
40%, 43% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -30px, 0);
|
||||
transform: translate3d(0, -30px, 0);
|
||||
}
|
||||
|
||||
70% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -15px, 0);
|
||||
transform: translate3d(0, -15px, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
-webkit-transform: translate3d(0,-4px,0);
|
||||
transform: translate3d(0,-4px,0);
|
||||
}
|
||||
}
|
||||
|
||||
.bounce {
|
||||
-webkit-animation-name: bounce;
|
||||
animation-name: bounce;
|
||||
-webkit-transform-origin: center bottom;
|
||||
-ms-transform-origin: center bottom;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.animated {
|
||||
-webkit-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animated.infinite {
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.animated.hinge {
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
let count = 0;
|
||||
const bounce = document.getElementById('click-me');
|
||||
var count = 0,
|
||||
bounce = document.getElementById('click-me');
|
||||
bounce.onclick = function() {
|
||||
count++;
|
||||
bounce.className = '';
|
||||
|
||||
135
public/5xx.css
135
public/5xx.css
@@ -1,135 +0,0 @@
|
||||
body {
|
||||
background: #00A9EA;
|
||||
color: white;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
text-align: center;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 250px;
|
||||
color: #fff;
|
||||
opacity: 0.5;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
p strong {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1 {
|
||||
font-size: 125px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p strong {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
-webkit-transform: translateY(50%);
|
||||
-ms-transform: translateY(50%);
|
||||
transform: translateY(50%);
|
||||
}
|
||||
|
||||
@-webkit-keyframes bounce {
|
||||
0%, 20%, 53%, 80%, 100% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
|
||||
40%, 43% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -30px, 0);
|
||||
transform: translate3d(0, -30px, 0);
|
||||
}
|
||||
|
||||
70% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -15px, 0);
|
||||
transform: translate3d(0, -15px, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
-webkit-transform: translate3d(0,-4px,0);
|
||||
transform: translate3d(0,-4px,0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 53%, 80%, 100% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
-webkit-transform: translate3d(0,0,0);
|
||||
transform: translate3d(0,0,0);
|
||||
}
|
||||
|
||||
40%, 43% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -30px, 0);
|
||||
transform: translate3d(0, -30px, 0);
|
||||
}
|
||||
|
||||
70% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
-webkit-transform: translate3d(0, -15px, 0);
|
||||
transform: translate3d(0, -15px, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
-webkit-transform: translate3d(0,-4px,0);
|
||||
transform: translate3d(0,-4px,0);
|
||||
}
|
||||
}
|
||||
|
||||
.bounce {
|
||||
-webkit-animation-name: bounce;
|
||||
animation-name: bounce;
|
||||
-webkit-transform-origin: center bottom;
|
||||
-ms-transform-origin: center bottom;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.animated {
|
||||
-webkit-animation-duration: 1s;
|
||||
animation-duration: 1s;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animated.infinite {
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.animated.hinge {
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "رقم الصفحة غير صحيح ، يجب أن يكون بين %1 و %2 .",
|
||||
"username-taken": "اسم المستخدم مأخوذ",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "البريد الالكتروني مأخوذ",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Вече има папка с това име",
|
||||
"invalid-pagination-value": "Грешен номер на странициране, трябва да бъде между %1 и %2",
|
||||
"username-taken": "Потребителското име е заето",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Е-пощата е заета",
|
||||
"email-nochange": "Въведената е-поща е същата като съществуващата.",
|
||||
"email-invited": "На тази е-поща вече е била изпратена покана",
|
||||
"email-not-confirmed": "Публикуването в някои категории и теми ще бъде възможно едва след като е-пощата Ви бъде потвърдена. Щръкнете тук, за да Ви изпратим е-писмо за потвърждение.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "ইউজারনেম আগেই ব্যবহৃত",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "ইমেইল আগেই ব্যবহৃত",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Neplatná hodnota stránkování, musí být alespoň %1 a nejvýše %2",
|
||||
"username-taken": "Uživatelské jméno je již použito",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Tento e-mail je již použit",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Ugyldig side værdi, skal mindst være %1 og maks. %2",
|
||||
"username-taken": "Brugernavn optaget",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Emailadresse allerede i brug",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Ordner existiert",
|
||||
"invalid-pagination-value": "Ungültige Seitennummerierung, muss mindestens %1 und maximal %2 sein",
|
||||
"username-taken": "Der Benutzername ist bereits vergeben",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-Mail-Adresse vergeben",
|
||||
"email-nochange": "Die eingegebene E-Mail ist die gleiche wie die bereits hinterlegte E-Mail.",
|
||||
"email-invited": "E-Mail wurde bereits eingeladen",
|
||||
"email-not-confirmed": "Das Schreiben von Beiträgen in einigen Kategorien oder Themen ist erst möglich, wenn Ihre E-Mail bestätigt wurde. Bitte klicken Sie hier, um eine Bestätigungs-E-Mail zu senden.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Το όνομα χρήστη είναι πιασμένο",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Το email είναι πιασμένο",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
|
||||
"username-taken": "Username taken",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email taken",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Username taken",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email taken",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Username taken",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email taken",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Número de página inválido, debe estar entre %1 y %2",
|
||||
"username-taken": "Nombre de usuario ocupado",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Correo electrónico ocupado",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Väär lehekülje numeratsioon, peab olema vähemalt %1 ja kõige rohkem %2",
|
||||
"username-taken": "Kasutajanimi on juba võetud",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email on võetud",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "ارزش گذاری صفحه نامعتبر است، کمترین مقدار <strong>%1</strong> و بیشترین مقدار <strong>%2</strong> باید باشد",
|
||||
"username-taken": "این نام کاربری گرفته شده است.",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "این ایمیل گرفته شده است.",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "ایمیل قبلا دعوت شدهاست",
|
||||
"email-not-confirmed": "پس از تایید ایمیل شما، ارسال در برخی دسته ها یا موضوعات فعال می شود، لطفاً برای ارسال ایمیل تایید اینجا را کلیک کنید.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Käyttäjänimi varattu",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Sähköpostiosoite varattu",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Le dossier existe",
|
||||
"invalid-pagination-value": "Valeur de pagination invalide. Celle-ci doit être comprise entre %1 et %2.",
|
||||
"username-taken": "Ce nom d'utilisateur est déjà pris",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-mail déjà utilisé",
|
||||
"email-nochange": "Le mail saisi est déjà enregistré.",
|
||||
"email-invited": "Cet utilisateur a déjà été invité.",
|
||||
"email-not-confirmed": "La publication dans certaines catégories ou sujets sera activée après confirmation de l'e-mail, veuillez cliquer ici pour envoyer un e-mail de confirmation.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Valor de paxinación incorreto, ten que estar entre %1 e %2",
|
||||
"username-taken": "Nome de usuario en uso",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Enderezo electrónico en uso",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "התיקיה קיימת",
|
||||
"invalid-pagination-value": "ערך דף לא חוקי, חייב להיות לפחות %1 ולא מעל %2",
|
||||
"username-taken": "שם משתמש תפוס",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "כתובת דוא\"ל תפוסה",
|
||||
"email-nochange": "כתובת דוא\"ל שהוזן זהה לדוא\"ל שנמצא כבר",
|
||||
"email-invited": "נשלחה כבר הזמנה לדוא\"ל זה",
|
||||
"email-not-confirmed": "פרסום בקטגוריות או בנושאים מסוימים מופעל רק לאחר אישור הדוא\"ל שלכם, אנא לחצו כאן כדי לשלוח אימות לדוא\"ל שלכם.",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"user-flagged-user-multiple": "<strong>%1</strong>, <strong>%2</strong> ו-%3 אחרים דיווחו על פרופיל משתמש (%4)",
|
||||
"user-posted-to": "<strong>%1</strong> פרסם תגובה ל: <strong>%2</strong>",
|
||||
"user-posted-to-dual": "<strong>%1</strong> ו<strong>%2</strong> הגיבו ל: <strong>%3</strong>",
|
||||
"user-posted-to-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>%3</strong> הגיבו ל: <strong>%4</strong>",
|
||||
"user-posted-to-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>3%</strong> הגיבו ל: <strong>%4</strong>",
|
||||
"user-posted-to-multiple": "<strong>%1</strong>, <strong>%2</strong> ו-%3 אחרים הגיבו ל: <strong>%4</strong>",
|
||||
"user-posted-topic": "<strong>%1</strong> העלה נושא חדש: <strong>%2</strong>",
|
||||
"user-edited-post": "<strong>%1</strong> ערך פוסט ב: <strong>%2</strong>",
|
||||
@@ -59,7 +59,7 @@
|
||||
"user-posted-topic-in-category": "<strong>%1</strong> פרסם נושא חדש ב<strong>%2</strong>",
|
||||
"user-started-following-you": "<strong>%1</strong> התחיל לעקוב אחריך.",
|
||||
"user-started-following-you-dual": "<strong>%1</strong> ו-<strong>%2</strong> התחילו לעקוב אחריך.",
|
||||
"user-started-following-you-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>%3</strong> התחילו לעקוב אחריך.",
|
||||
"user-started-following-you-triple": "<strong>%1</strong>, <strong>%2</strong> ו<strong>3%</strong> התחילו לעקוב אחריך.",
|
||||
"user-started-following-you-multiple": "<strong>%1</strong>, <strong>%2</strong> ו-%3 אחרים התחילו לעקוב אחריך.",
|
||||
"new-register": "<strong>%1</strong> שלח בקשת הרשמה.",
|
||||
"new-register-multiple": "ישנן <strong>%1</strong> בקשות הרשמה שמחכות לבדיקה.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Netočno numeriranje stranica, mora biti %1 ili %2",
|
||||
"username-taken": "Korisničko ime je zauzeto",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email je zauzet",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Mappa létezik",
|
||||
"invalid-pagination-value": "Érvénytelen lapozási érték, legalább %1 kell lennie és legfeljebb %2 -nak/nek",
|
||||
"username-taken": "Foglalt felhasználónév",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Foglalt e-mail",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Ez az email cím már meg lett hívva",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"edit-privileges": "Խմբագրել արտոնությունները",
|
||||
"select-clear-all": "Ընտրել/Մաքրել բոլորը",
|
||||
"chat": "Զրույց",
|
||||
"chat-with-privileged": "Խոսել առավելություն ունեցողի հետ",
|
||||
"chat-with-privileged": "Chat with Privileged",
|
||||
"upload-images": "Վերբեռնեք պատկերներ",
|
||||
"upload-files": "Վերբեռնել Ֆայլեր",
|
||||
"signature": "Ստորագրություն",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Թղթապանակ գոյություն ունի",
|
||||
"invalid-pagination-value": "Էջավորման անվավեր արժեքը, պետք է լինի առնվազն %1 և առավելագույնը %2",
|
||||
"username-taken": "Օգտագործողի անունը վերցված է",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Էլփոստը վերցված է",
|
||||
"email-nochange": "Մուտքագրված էլփոստը նույնն է, ինչ ֆայլում արդեն առկա էլ.",
|
||||
"email-invited": "Էլփոստն արդեն հրավիրված էր",
|
||||
"email-not-confirmed": "Որոշ կատեգորիաներում կամ թեմաներում հրապարակելը միացված կլինի, երբ ձեր էլփոստը հաստատվի, խնդրում ենք սեղմել այստեղ՝ հաստատող էլփոստը ուղարկելու համար:",
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
"sessions.description": "Այս էջը թույլ է տալիս դիտել ցանկացած ակտիվ սեանս այս ֆորումում և անհրաժեշտության դեպքում չեղարկել դրանք: Դուք կարող եք չեղարկել ձեր սեփական սեանսը՝ դուրս գալով ձեր հաշվից:",
|
||||
"revoke-session": "Չեղյալ համարել նիստը",
|
||||
"browser-version-on-platform": "%1 %2 %3-ում",
|
||||
"consent.title": "Ձեր Իրավունքները և Համաձայնությունը",
|
||||
"consent.title": "Your Rights & Consent",
|
||||
"consent.lead": "Այս համայնքի ֆորումը հավաքում և մշակում է ձեր անձնական տվյալները:",
|
||||
"consent.intro": "Մենք օգտագործում ենք այս տեղեկատվությունը խստորեն այս համայնքում ձեր փորձառությունն անհատականացնելու, ինչպես նաև ձեր կատարած գրառումները ձեր օգտատիրոջ հաշվին կապելու համար: Գրանցման քայլի ընթացքում ձեզանից պահանջվել է տրամադրել օգտատիրոջ անուն և էլ.փոստի հասցե, դուք կարող եք նաև լրացուցիչ տեղեկություններ տրամադրել այս կայքում ձեր օգտատիրոջ պրոֆիլը լրացնելու համար: Մենք պահպանում ենք այս տեղեկատվությունը ձեր օգտատիրոջ հաշվի ողջ կյանքի ընթացքում, և դուք կարող եք հետ վերցնել համաձայնությունը: ցանկացած պահի ջնջելով ձեր հաշիվը: Ցանկացած ժամանակ դուք կարող եք պահանջել ձեր ներդրման պատճենը այս կայքում՝ ձեր իրավունքների և amp; Համաձայնության էջ: Եթե ունեք հարցեր կամ մտահոգություններ, խորհուրդ ենք տալիս դիմել այս ֆորումի ադմինիստրատիվ թիմին:",
|
||||
"consent.email-intro": "Երբեմն, մենք կարող ենք նամակներ ուղարկել ձեր գրանցված էլ․ հասցեին՝ թարմացումներ տրամադրելու և/կամ ձեզ ծանուցելու նոր գործունեության մասին, որը վերաբերում է ձեզ: Դուք կարող եք հարմարեցնել համայնքի ամփոփման հաճախականությունը (ներառյալ այն ուղղակիորեն անջատելը), ինչպես նաև ընտրել, թե ինչ տեսակի ծանուցումներ պետք է ստանալ էլփոստի միջոցով՝ ձեր օգտվողի կարգավորումների էջի միջոցով:",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Nomor pagination tidak valid, minimal %1 dan maksimal %2",
|
||||
"username-taken": "Username sudah terdaftar",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email sudah terdaftar",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "La cartella esiste",
|
||||
"invalid-pagination-value": "Valore di impaginazione non valido, deve essere almeno %1 ed al massimo %2",
|
||||
"username-taken": "Nome utente già esistente",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email già esistente",
|
||||
"email-nochange": "L'email inserita è la stessa dell'email già presente in archivio.",
|
||||
"email-invited": "L'email è già stata invitata",
|
||||
"email-not-confirmed": "Sarai abilitato a postare in alcune categorie o discussioni una volta che la tua email sarà confermata, per favore clicca qui per inviare una email di conferma.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "無効なページネーション値です。%1 から%2の値でなければありません。",
|
||||
"username-taken": "ユーザー名は既に使われています",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "メールアドレスは既に使われています",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "폴더가 이미 존재합니다.",
|
||||
"invalid-pagination-value": "올바르지 않은 페이지 값입니다. 최소 %1에서 최대 2% 사이로 설정해야 합니다.",
|
||||
"username-taken": "이미 사용 중인 사용자명입니다.",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "이미 사용 중인 이메일입니다.",
|
||||
"email-nochange": "입력한 전자 메일이 이미 등록되어 있는 전자 메일과 동일합니다.",
|
||||
"email-invited": "해당 이메일의 사용자는 이미 초대되었습니다.",
|
||||
"email-not-confirmed": "이메일 인증이 완료된 후 카테고리나 화제에 새로운 포스트를 작성할 수 있습니다. 여기를 눌러 인증 메일을 다시 발송할 수 있습니다.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Bloga puslapių išdėstymo reikšmė. Ji turėtų būti ne mažesnė nei %1 ir ne didesnė nei %2",
|
||||
"username-taken": "Vartotojo vardas jau užimtas",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "El. pašto adresas jau užimtas",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Nederīgs vienību skaits, ir jābūt vismaz %1 un ne vairāk kā %2",
|
||||
"username-taken": "Lietotājvārds jau izmantots",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-pasta adrese jau izmantota",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Nombor halaman tidak sah, mesti tidak kurang dari %1 dan tidak lebih dari %2",
|
||||
"username-taken": "Nama pengguna telah digunakan",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Emel telah digunakan",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Mappen eksisterer",
|
||||
"invalid-pagination-value": "Ugyldig sidetall, må være minst %1 og maks %2",
|
||||
"username-taken": "Brukernavn opptatt",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-post opptatt",
|
||||
"email-nochange": "E-posten som er angitt er den samme e-posten som allerede er lagret.",
|
||||
"email-invited": "E-post har allerede fått invitasjon",
|
||||
"email-not-confirmed": "Posting i enkelte kategorier eller emner blir aktivert når e-posten din er bekreftet. Klikk her for å sende en bekreftelses-e-post.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Ongeldig paginering waarde. De waarde moet op z'n minst %1 zijn en niet hoger dan %2 zijn.",
|
||||
"username-taken": "Gebruikersnaam is al in gebruik",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-mailadres is al in gebruik",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "E-mail was reeds uitgenodigd",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder istnieje",
|
||||
"invalid-pagination-value": "Błędna wartość paginacji, zakres od %1 do %2",
|
||||
"username-taken": "Login zajęty",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email zajęty",
|
||||
"email-nochange": "Podany email jest taki sam jak ten już zapisany.",
|
||||
"email-invited": "Ten adres email otrzymał już zaproszenie",
|
||||
"email-not-confirmed": "Pisanie w niektórych kategoriach albo tematach jest dozwolone wtedy gdy Twój adres email został zweryfikowany, proszę kliknij tutaj aby wysłać potwierdzający email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Valor de paginação inválido, precisa ser no mínimo %1 e no máximo %2",
|
||||
"username-taken": "Nome de usuário já existe",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email já cadastrado",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "O email já foi convidado",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Valor de paginação errado, deve ser no mínimo %1 e no máximo %2",
|
||||
"username-taken": "Nome de utilizar já utilizado",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-mail já utilizado",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Numele de utilizator este deja folosit",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Adresa de email este deja folostă",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Папка существует",
|
||||
"invalid-pagination-value": "Неправильно указан номер страницы. Значение должно быть в диапазоне от %1 до %2",
|
||||
"username-taken": "Это имя пользователя уже занято",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Пользователь с таким адресом электронной почты уже зарегистрирован",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Электронная почта уже была приглашена",
|
||||
"email-not-confirmed": "Вы не сможете отправлять сообщения, пока ваш адрес электронной почты не подтверждён. Пожалуйста, нажмите здесь, чтобы подтвердить его.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Izina ryarafashwe mbere",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email yarafashwe mbere",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Invalid pagination value, must be at least %1 and at most %2",
|
||||
"username-taken": "Username taken",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email taken",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Neplatná stránkovania hodnota, musí byť najmenej %1 a najviac %2",
|
||||
"username-taken": "Užívateľské meno je už obsadené",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Tento e-mail je už obsadený",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Napačna vrednost za številčenje strani. Vrednost mora biti najmanj %1 in največ %2.",
|
||||
"username-taken": "Uporabniško ime je že zasedeno.",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-poštni naslov je že zaseden.",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Ky dokument ekziston",
|
||||
"invalid-pagination-value": "Vlera e pasaktë e faqes, duhet të jetë së paku %1 dhe maksimumi %2",
|
||||
"username-taken": "Username është i zënë",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email-i është i zënë",
|
||||
"email-nochange": "Email-i i futur është i njëjtë me emailin ekzistues në sistem.",
|
||||
"email-invited": "Email-i është ftuar më herët",
|
||||
"email-not-confirmed": "Postimi në disa kategori ose tema aktivizohet pasi emaili juaj të konfirmohet, ju lutemi klikoni këtu për të dërguar një email konfirmimi.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Фасцикла постоји",
|
||||
"invalid-pagination-value": "Неважећа вредност приликом нумерисања страница, мора бити најмање %1 а највише %2",
|
||||
"username-taken": "Корисничко име је заузето",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Адреса е-поште је заузета",
|
||||
"email-nochange": "Унета е-пошта је иста као е-пошта која је већ у евиденцији.",
|
||||
"email-invited": "Е-пошта је већ позвана",
|
||||
"email-not-confirmed": "Објављивање у неким категоријама или темама је омогућено када потврдите вашу е-пошту, кликните овде да бисте послали е-поруку за потврду.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Ogiltigt värde för siduppdelning. Värdet måste vara mellan %1 och %2",
|
||||
"username-taken": "Användarnamn upptaget",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Epostadress upptagen",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "หมายเลขหน้าไม่ถูกต้อง จำเป็นต้องเป็นตัวเลขอย่างน้อย %1 และอย่างมาก %2",
|
||||
"username-taken": "ชื่อผู้ใช้นี้มีการใช้แล้ว",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "อีเมลนี้มีการใช้แล้ว",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Dosya mevcut",
|
||||
"invalid-pagination-value": "Geçersiz sayfa numarası girdiniz, en az %1 ve en fazla %2 olabilir",
|
||||
"username-taken": "Kullanıcı İsmi Alınmış",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "E-posta Alınmış",
|
||||
"email-nochange": "Girdiğiniz e-posta var olan e-posta ile aynı",
|
||||
"email-invited": "E-posta halihazırda davet edilmiş",
|
||||
"email-not-confirmed": "Ancak e-postanız onaylandıktan sonra bazı kategorilere veya konulara ileti gönderebilirsiniz; lütfen bir onay e-postası almak için buraya tıklayın.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "Невірне значення сторінки, має бути щонайменше %1 та щонайбільше %2",
|
||||
"username-taken": "Це ім'я зайняте",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Ця електронна пошта зайнята",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Thư mục tồn tại",
|
||||
"invalid-pagination-value": "Giá trị phân trang không hợp lệ, tối thiểu phải là %1 và tối đa là %2",
|
||||
"username-taken": "Tên đăng nhập đã tồn tại",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "Email đã được đăng kí",
|
||||
"email-nochange": "Email đã nhập giống với email đã có trong tệp.",
|
||||
"email-invited": "Email đã được mời",
|
||||
"email-not-confirmed": "Đăng trong một số danh mục hoặc chủ đề được bật sau khi email của bạn được xác nhận, vui lòng nhấp vào đây để gửi email xác nhận.",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "文件夹已存在",
|
||||
"invalid-pagination-value": "无效的分页数值,必须介于 %1 和 %2 之间",
|
||||
"username-taken": "此用户名已被占用",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "此电子邮箱已被占用",
|
||||
"email-nochange": "输入的邮件地址和已存档的邮件地址相同。",
|
||||
"email-invited": "已通过电子邮件进行邀请",
|
||||
"email-not-confirmed": "您需要验证您的邮箱后才能在版块或主题中发布帖子,请点击此处以发送验证邮件。",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"folder-exists": "Folder exists",
|
||||
"invalid-pagination-value": "無效的分頁數,必須介於 %1 和 %2 之間",
|
||||
"username-taken": "此使用者名已被使用",
|
||||
"email-taken": "Email address is already taken.",
|
||||
"email-taken": "此電子信箱已被使用",
|
||||
"email-nochange": "The email entered is the same as the email already on file.",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "Posting in some categories or topics is enabled once your email is confirmed, please click here to send a confirmation email.",
|
||||
|
||||
@@ -150,7 +150,7 @@ ajaxify.widgets = { render: render };
|
||||
|
||||
if (data) {
|
||||
let status = parseInt(data.status, 10);
|
||||
if ([400, 403, 404, 500, 502, 503].includes(status)) {
|
||||
if ([400, 403, 404, 500, 502, 504].includes(status)) {
|
||||
if (status === 502 && retry) {
|
||||
retry = false;
|
||||
ajaxifyTimer = undefined;
|
||||
|
||||
@@ -10,6 +10,7 @@ const isPrerelease = /^v?\d+\.\d+\.\d+-.+$/;
|
||||
const latestReleaseUrl = 'https://api.github.com/repos/NodeBB/NodeBB/releases/latest';
|
||||
|
||||
async function getLatestVersion() {
|
||||
return '';
|
||||
const headers = {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
'User-Agent': encodeURIComponent(`NodeBB Admin Control Panel/${meta.config.title}`),
|
||||
@@ -18,24 +19,25 @@ async function getLatestVersion() {
|
||||
if (versionCacheLastModified) {
|
||||
headers['If-Modified-Since'] = versionCacheLastModified;
|
||||
}
|
||||
try {
|
||||
const { body: latestRelease, response } = await request.get(latestReleaseUrl, {
|
||||
headers: headers,
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
const { body: latestRelease, response } = await request.get(latestReleaseUrl, {
|
||||
headers: headers,
|
||||
timeout: 2000,
|
||||
});
|
||||
if (response.statusCode === 304) {
|
||||
if (!latestRelease || !latestRelease.tag_name) {
|
||||
throw new Error('[[error:cant-get-latest-release]]');
|
||||
}
|
||||
const tagName = latestRelease.tag_name.replace(/^v/, '');
|
||||
versionCache = tagName;
|
||||
versionCacheLastModified = response.headers['last-modified'];
|
||||
return versionCache;
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 304) {
|
||||
return versionCache;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
if (!latestRelease || !latestRelease.tag_name) {
|
||||
throw new Error('[[error:cant-get-latest-release]]');
|
||||
}
|
||||
const tagName = latestRelease.tag_name.replace(/^v/, '');
|
||||
versionCache = tagName;
|
||||
versionCacheLastModified = response.headers['last-modified'];
|
||||
return versionCache;
|
||||
}
|
||||
|
||||
exports.getLatestVersion = getLatestVersion;
|
||||
|
||||
@@ -7,7 +7,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
|
||||
|
||||
const request = require('../request');
|
||||
const { paths, pluginNamePattern } = require('../constants');
|
||||
const pkgInstall = require('./package-install');
|
||||
|
||||
@@ -74,11 +74,7 @@ async function getCurrentVersion() {
|
||||
}
|
||||
|
||||
async function getSuggestedModules(nbbVersion, toCheck) {
|
||||
const request = require('../request');
|
||||
let { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unable to get suggested module for NodeBB(${nbbVersion}) ${toCheck.join(',')}`);
|
||||
}
|
||||
let { body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`);
|
||||
if (!Array.isArray(body) && toCheck.length === 1) {
|
||||
body = [body];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const nconf = require('nconf');
|
||||
const winston = require('winston');
|
||||
const validator = require('validator');
|
||||
const path = require('path');
|
||||
const translator = require('../translator');
|
||||
const plugins = require('../plugins');
|
||||
const middleware = require('../middleware');
|
||||
@@ -56,12 +54,6 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
|
||||
controllers['404'].handle404(req, res);
|
||||
};
|
||||
|
||||
const notBuiltHandler = async () => {
|
||||
let file = await fs.promises.readFile(path.join(__dirname, '../../public/500.html'), { encoding: 'utf-8' });
|
||||
file = file.replace('{message}', 'Failed to lookup view! Did you run `./nodebb build`?');
|
||||
return res.type('text/html').send(file);
|
||||
};
|
||||
|
||||
const defaultHandler = async function () {
|
||||
if (res.headersSent) {
|
||||
return;
|
||||
@@ -103,8 +95,6 @@ exports.handleErrors = async function handleErrors(err, req, res, next) { // esl
|
||||
data.cases[err.code](err, req, res, defaultHandler);
|
||||
} else if (err.message.startsWith('[[error:no-') && err.message !== '[[error:no-privileges]]') {
|
||||
notFoundHandler();
|
||||
} else if (err.message.startsWith('Failed to lookup view')) {
|
||||
notBuiltHandler();
|
||||
} else {
|
||||
await defaultHandler();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = function (middleware) {
|
||||
|
||||
const data = {
|
||||
site_title: meta.config.title || 'NodeBB',
|
||||
message: meta.config.maintenanceModeMessage,
|
||||
message: meta.config.maintenanceModeMessage || '',
|
||||
};
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
|
||||
@@ -153,10 +153,8 @@ Plugins.reloadRoutes = async function (params) {
|
||||
|
||||
Plugins.get = async function (id) {
|
||||
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins/${id}`;
|
||||
const { response, body } = await request.get(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`[[error:unable-to-load-plugin, ${id}]]`);
|
||||
}
|
||||
const { body } = await request.get(url);
|
||||
|
||||
let normalised = await Plugins.normalise([body ? body.payload : {}]);
|
||||
normalised = normalised.filter(plugin => plugin.id === id);
|
||||
return normalised.length ? normalised[0] : undefined;
|
||||
@@ -169,10 +167,7 @@ Plugins.list = async function (matching) {
|
||||
const { version } = require(paths.currentPackage);
|
||||
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins${matching !== false ? `?version=${version}` : ''}`;
|
||||
try {
|
||||
const { response, body } = await request.get(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`[[error:unable-to-load-plugins-from-nbbpm]]`);
|
||||
}
|
||||
const { body } = await request.get(url);
|
||||
return await Plugins.normalise(body);
|
||||
} catch (err) {
|
||||
winston.error(`Error loading ${url}`, err);
|
||||
@@ -182,10 +177,7 @@ Plugins.list = async function (matching) {
|
||||
|
||||
Plugins.listTrending = async () => {
|
||||
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/analytics/top/week`;
|
||||
const { response, body } = await request.get(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`[[error:unable-to-load-trending-plugins]]`);
|
||||
}
|
||||
const { body } = await request.get(url);
|
||||
return body;
|
||||
};
|
||||
|
||||
|
||||
@@ -74,10 +74,8 @@ module.exports = function (Plugins) {
|
||||
};
|
||||
|
||||
Plugins.checkWhitelist = async function (id, version) {
|
||||
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
|
||||
}
|
||||
const { body } = await request.get(`https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`);
|
||||
|
||||
if (body && body.code === 'ok' && (version === 'latest' || body.payload.valid.includes(version))) {
|
||||
return;
|
||||
}
|
||||
@@ -86,10 +84,7 @@ module.exports = function (Plugins) {
|
||||
};
|
||||
|
||||
Plugins.suggest = async function (pluginId, nbbVersion) {
|
||||
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
|
||||
}
|
||||
const { body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`);
|
||||
return body;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ module.exports = function (Plugins) {
|
||||
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugin/usage`;
|
||||
try {
|
||||
const { response, body } = await request.post(url, {
|
||||
body: {
|
||||
data: {
|
||||
id: hash.digest('hex'),
|
||||
version: pkg.version,
|
||||
plugins: Plugins.loadedPlugins,
|
||||
@@ -35,7 +35,7 @@ module.exports = function (Plugins) {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status !== 200) {
|
||||
winston.error(`[plugins.submitUsageData] received ${response.status} ${body}`);
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,80 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios').default;
|
||||
const { CookieJar } = require('tough-cookie');
|
||||
const fetchCookie = require('fetch-cookie');
|
||||
const { wrapper } = require('axios-cookiejar-support');
|
||||
|
||||
wrapper(axios);
|
||||
|
||||
exports.jar = function () {
|
||||
return new CookieJar();
|
||||
};
|
||||
|
||||
async function call(url, method, { body, timeout, jar, ...config } = {}) {
|
||||
let fetchImpl = fetch;
|
||||
if (jar) {
|
||||
fetchImpl = fetchCookie(fetch, jar);
|
||||
}
|
||||
|
||||
const opts = {
|
||||
async function call(url, method, config = {}) {
|
||||
const result = await axios({
|
||||
...config,
|
||||
method,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
...config.headers,
|
||||
},
|
||||
};
|
||||
if (timeout > 0) {
|
||||
opts.signal = AbortSignal.timeout(timeout);
|
||||
}
|
||||
|
||||
if (body && ['POST', 'PUT', 'PATCH', 'DEL', 'DELETE'].includes(method)) {
|
||||
if (opts.headers['content-type'] && opts.headers['content-type'].startsWith('application/json')) {
|
||||
opts.body = JSON.stringify(body);
|
||||
} else {
|
||||
opts.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetchImpl(url, opts);
|
||||
|
||||
const { headers } = response;
|
||||
const contentType = headers.get('content-type');
|
||||
const jsonTest = /application\/([a-z]+\+)?json/;
|
||||
const isJSON = contentType && jsonTest.test(contentType);
|
||||
let respBody = await response.text();
|
||||
if (isJSON && respBody) {
|
||||
try {
|
||||
respBody = JSON.parse(respBody);
|
||||
} catch (err) {
|
||||
throw new Error('invalid json in response body', url);
|
||||
}
|
||||
}
|
||||
url: url,
|
||||
});
|
||||
|
||||
return {
|
||||
body: respBody,
|
||||
body: result.data,
|
||||
response: {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
statusCode: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
status: result.status,
|
||||
statusCode: result.status,
|
||||
statusText: result.statusText,
|
||||
headers: result.headers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
const { body, response } = await request.get('someurl?foo=1&baz=2')
|
||||
or
|
||||
const { body, response } = await request.get('someurl', { params: { foo:1, baz: 2 } })
|
||||
*/
|
||||
exports.get = async (url, config) => call(url, 'GET', config);
|
||||
exports.get = async (url, config) => call(url, 'get', config);
|
||||
|
||||
exports.head = async (url, config) => call(url, 'HEAD', config);
|
||||
exports.del = async (url, config) => call(url, 'DELETE', config);
|
||||
exports.head = async (url, config) => call(url, 'head', config);
|
||||
exports.del = async (url, config) => call(url, 'delete', config);
|
||||
exports.delete = exports.del;
|
||||
exports.options = async (url, config) => call(url, 'OPTIONS', config);
|
||||
exports.options = async (url, config) => call(url, 'delete', config);
|
||||
|
||||
/*
|
||||
const { body, response } = await request.post('someurl', { body: { foo: 1, baz: 2}})
|
||||
const { body, response } = await request.post('someurl', { data: { foo: 1, baz: 2}})
|
||||
*/
|
||||
exports.post = async (url, config) => call(url, 'POST', config);
|
||||
exports.put = async (url, config) => call(url, 'PUT', config);
|
||||
exports.patch = async (url, config) => call(url, 'PATCH', config);
|
||||
exports.post = async (url, config) => call(url, 'post', config);
|
||||
exports.put = async (url, config) => call(url, 'put', config);
|
||||
exports.patch = async (url, config) => call(url, 'patch', config);
|
||||
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ module.exports = function (app, middleware, controllers) {
|
||||
app.get('/css/previews/:theme', controllers.admin.themes.get);
|
||||
app.get('/osd.xml', controllers.osd.handle);
|
||||
app.get('/service-worker.js', (req, res) => {
|
||||
res.status(200)
|
||||
.type('application/javascript')
|
||||
.set('Service-Worker-Allowed', `${nconf.get('relative_path')}/`)
|
||||
.sendFile(path.join(__dirname, '../../build/public/src/service-worker.js'));
|
||||
res.status(200).type('application/javascript').set('Service-Worker-Allowed', `${nconf.get('relative_path')}/`).sendFile(path.join(__dirname, '../../public/src/service-worker.js'));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -27,7 +27,6 @@ module.exports = function (SocketPosts) {
|
||||
canDelete: privileges.posts.canDelete(data.pid, socket.uid),
|
||||
canPurge: privileges.posts.canPurge(data.pid, socket.uid),
|
||||
canFlag: privileges.posts.canFlag(data.pid, socket.uid),
|
||||
canViewHistory: privileges.posts.can('posts:history', data.pid, socket.uid),
|
||||
flagged: flags.exists('post', data.pid, socket.uid), // specifically, whether THIS calling user flagged
|
||||
bookmarked: posts.hasBookmarked(data.pid, socket.uid),
|
||||
postSharing: social.getActivePostSharing(),
|
||||
@@ -47,7 +46,7 @@ module.exports = function (SocketPosts) {
|
||||
postData.display_move_tools = results.isAdmin || results.isModerator;
|
||||
postData.display_change_owner_tools = results.isAdmin || results.isModerator;
|
||||
postData.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !postData.selfPost;
|
||||
postData.display_history = results.history && results.canViewHistory;
|
||||
postData.display_history = results.history;
|
||||
postData.flags = {
|
||||
flagId: parseInt(results.posts.flagId, 10) || null,
|
||||
can: results.canFlag.flag,
|
||||
|
||||
10
test/api.js
10
test/api.js
@@ -493,13 +493,13 @@ describe('API', async () => {
|
||||
|
||||
try {
|
||||
if (type === 'json') {
|
||||
const searchParams = new URLSearchParams(qs);
|
||||
result = await request[method](`${url}?${searchParams}`, {
|
||||
result = await request[method](url, {
|
||||
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null, // don't throw on non-200 (e.g. 302)
|
||||
headers: headers,
|
||||
body: body,
|
||||
params: qs,
|
||||
data: body,
|
||||
});
|
||||
} else if (type === 'form') {
|
||||
result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken);
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('authentication', () => {
|
||||
|
||||
const { body } = await request.post(`${nconf.get('url')}/register`, {
|
||||
jar,
|
||||
body: {
|
||||
data: {
|
||||
email: 'admin@nodebb.org',
|
||||
username: 'admin',
|
||||
password: 'adminpwd',
|
||||
@@ -133,6 +133,7 @@ describe('authentication', () => {
|
||||
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/me`, {
|
||||
jar: jar,
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.statusCode, 401);
|
||||
assert.strictEqual(body.status.code, 'not-authorised');
|
||||
@@ -167,9 +168,9 @@ describe('authentication', () => {
|
||||
function getCookieExpiry(response) {
|
||||
const { headers } = response;
|
||||
assert(headers['set-cookie']);
|
||||
assert.strictEqual(headers['set-cookie'].includes('Expires'), true);
|
||||
assert.strictEqual(headers['set-cookie'][0].includes('Expires'), true);
|
||||
|
||||
const values = headers['set-cookie'].split(';');
|
||||
const values = headers['set-cookie'][0].split(';');
|
||||
return values.reduce((memo, cur) => {
|
||||
if (!memo) {
|
||||
const [name, value] = cur.split('=');
|
||||
@@ -205,7 +206,7 @@ describe('authentication', () => {
|
||||
|
||||
assert(response.headers);
|
||||
assert(response.headers['set-cookie']);
|
||||
assert.strictEqual(response.headers['set-cookie'].includes('Expires'), false);
|
||||
assert.strictEqual(response.headers['set-cookie'][0].includes('Expires'), false);
|
||||
});
|
||||
|
||||
it('should set a different expiry if sessionDuration is set', async () => {
|
||||
@@ -271,11 +272,12 @@ describe('authentication', () => {
|
||||
const csrf_token = await helpers.getCsrfToken(jar);
|
||||
|
||||
const { response } = await request.post(`${nconf.get('url')}/login`, {
|
||||
body: {
|
||||
data: {
|
||||
username: 'regular',
|
||||
password: 'regularpwd',
|
||||
},
|
||||
jar: jar,
|
||||
validateStatus: () => true,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
'x-forwarded-for': '<script>alert("xss")</script>',
|
||||
@@ -316,7 +318,7 @@ describe('authentication', () => {
|
||||
});
|
||||
|
||||
it('should fail to login if password is longer than 4096', async () => {
|
||||
let longPassword = '';
|
||||
let longPassword;
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
longPassword += 'a';
|
||||
}
|
||||
@@ -513,7 +515,10 @@ describe('authentication', () => {
|
||||
});
|
||||
|
||||
it('should fail with invalid token', async () => {
|
||||
const { response, body } = await helpers.request('get', `/api/self?_uid${newUid}`, {
|
||||
const { response, body } = await helpers.request('get', `/api/self`, {
|
||||
data: {
|
||||
_uid: newUid,
|
||||
},
|
||||
jar: jar,
|
||||
headers: {
|
||||
Authorization: `Bearer sdfhaskfdja-jahfdaksdf`,
|
||||
@@ -525,6 +530,7 @@ describe('authentication', () => {
|
||||
|
||||
it('should use a token tied to an uid', async () => {
|
||||
const { response, body } = await helpers.request('get', `/api/self`, {
|
||||
json: true,
|
||||
headers: {
|
||||
Authorization: `Bearer ${userToken}`,
|
||||
},
|
||||
@@ -536,6 +542,8 @@ describe('authentication', () => {
|
||||
|
||||
it('should fail if _uid is not passed in with master token', async () => {
|
||||
const { response, body } = await helpers.request('get', `/api/self`, {
|
||||
data: {},
|
||||
json: true,
|
||||
headers: {
|
||||
Authorization: `Bearer ${masterToken}`,
|
||||
},
|
||||
@@ -546,7 +554,11 @@ describe('authentication', () => {
|
||||
});
|
||||
|
||||
it('should use master api token and _uid', async () => {
|
||||
const { response, body } = await helpers.request('get', `/api/self?_uid=${newUid}`, {
|
||||
const { response, body } = await helpers.request('get', `/api/self`, {
|
||||
data: {
|
||||
_uid: newUid,
|
||||
},
|
||||
json: true,
|
||||
headers: {
|
||||
Authorization: `Bearer ${masterToken}`,
|
||||
},
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('Categories', () => {
|
||||
});
|
||||
|
||||
it('should load a category route', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/${categoryObj.cid}/test-category`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/${categoryObj.cid}/test-category`, { json: true });
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(body.name, 'Test Category & NodeBB');
|
||||
assert(body);
|
||||
|
||||
@@ -69,6 +69,7 @@ describe('Admin Controllers', () => {
|
||||
({ jar } = await helpers.loginUser('admin', 'barbar'));
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/admin`, {
|
||||
jar: jar,
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 403);
|
||||
@@ -165,7 +166,7 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 for edit/email page if user does not exist', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/doesnotexist/edit/email`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/doesnotexist/edit/email`, { jar: jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -242,7 +243,9 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 if users is not privileged', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/registration-queue`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/registration-queue`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
@@ -278,7 +281,10 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('should return 403 if no referer', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/groups/administrators/csv`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/groups/administrators/csv`, {
|
||||
jar,
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.statusCode, 403);
|
||||
assert.equal(body, '[[error:invalid-origin]]');
|
||||
});
|
||||
@@ -286,6 +292,7 @@ describe('Admin Controllers', () => {
|
||||
it('should return 403 if referer is not /api/admin/groups/administrators/csv', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/groups/administrators/csv`, {
|
||||
jar: jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
referer: '/topic/1/test',
|
||||
},
|
||||
@@ -318,13 +325,16 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('should load /api/admin/advanced/cache/dump and 404 with no query param', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/advanced/cache/dump`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/advanced/cache/dump`, {
|
||||
jar,
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
|
||||
it('should load /api/admin/advanced/cache/dump', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/advanced/cache/dump?name=post`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin/advanced/cache/dump?name=post`, { jar: jar });
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert(body);
|
||||
});
|
||||
@@ -447,7 +457,9 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('/post-queue should 404 for regular user', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/post-queue`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/post-queue`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
assert(body);
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
@@ -459,7 +471,9 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('/ip-blacklist should 404 for regular user', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/ip-blacklist`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/ip-blacklist`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
assert(body);
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
@@ -500,7 +514,9 @@ describe('Admin Controllers', () => {
|
||||
});
|
||||
|
||||
it('should error with no privileges', async () => {
|
||||
const { body } = await request.get(`${nconf.get('url')}/api/flags`);
|
||||
const { body } = await request.get(`${nconf.get('url')}/api/flags`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(body, {
|
||||
status: {
|
||||
@@ -525,6 +541,7 @@ describe('Admin Controllers', () => {
|
||||
headers: {
|
||||
Accept: 'text/html, application/json',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
@@ -532,7 +549,7 @@ describe('Admin Controllers', () => {
|
||||
it('should error when you attempt to flag a privileged user\'s post', async () => {
|
||||
const { response, body } = await helpers.request('post', '/api/v3/flags', {
|
||||
jar: regularJar,
|
||||
body: {
|
||||
data: {
|
||||
id: pid,
|
||||
type: 'post',
|
||||
reason: 'spam',
|
||||
@@ -548,7 +565,7 @@ describe('Admin Controllers', () => {
|
||||
meta.config['min:rep:flag'] = 1000;
|
||||
const { response, body } = await helpers.request('post', '/api/v3/flags', {
|
||||
jar: regularJar,
|
||||
body: {
|
||||
data: {
|
||||
id: regularPid,
|
||||
type: 'post',
|
||||
reason: 'spam',
|
||||
@@ -566,7 +583,7 @@ describe('Admin Controllers', () => {
|
||||
meta.config['min:rep:flag'] = 0;
|
||||
await helpers.request('post', '/api/v3/flags', {
|
||||
jar: regularJar,
|
||||
body: {
|
||||
data: {
|
||||
id: regularPid,
|
||||
type: 'post',
|
||||
reason: 'spam',
|
||||
@@ -619,7 +636,9 @@ describe('Admin Controllers', () => {
|
||||
describe('admin page privileges', () => {
|
||||
let uid;
|
||||
const privileges = require('../src/privileges');
|
||||
const requestOpts = {};
|
||||
const requestOpts = {
|
||||
validateStatus: null,
|
||||
};
|
||||
before(async () => {
|
||||
uid = await user.create({ username: 'regularjoe', password: 'barbar' });
|
||||
requestOpts.jar = (await helpers.loginUser('regularjoe', 'barbar')).jar;
|
||||
|
||||
@@ -191,7 +191,7 @@ describe('Controllers', () => {
|
||||
|
||||
it('should 404 if custom does not exist', async () => {
|
||||
await meta.configs.set('homePageRoute', 'this-route-does-not-exist');
|
||||
const { response, body } = await request.get(nconf.get('url'));
|
||||
const { response, body } = await request.get(nconf.get('url'), { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
@@ -224,7 +224,9 @@ describe('Controllers', () => {
|
||||
const baseUrl = nconf.get('url');
|
||||
testRoutes.forEach((route) => {
|
||||
it(route.it, async () => {
|
||||
const { response, body } = await request.get(`${baseUrl}/${route.url}`);
|
||||
const { response, body } = await request.get(`${baseUrl}/${route.url}`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.statusCode, route.status || 200);
|
||||
if (route.body) {
|
||||
assert.strictEqual(String(body), route.body);
|
||||
@@ -236,15 +238,17 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should load /register/complete', async () => {
|
||||
const data = {
|
||||
username: 'interstitial',
|
||||
password: '123456',
|
||||
'password-confirm': '123456',
|
||||
email: 'test@me.com',
|
||||
};
|
||||
|
||||
const jar = request.jar();
|
||||
const csrf_token = await helpers.getCsrfToken(jar);
|
||||
const { response, body } = await request.post(`${nconf.get('url')}/register`, {
|
||||
body: {
|
||||
username: 'interstitial',
|
||||
password: '123456',
|
||||
'password-confirm': '123456',
|
||||
email: 'test@me.com',
|
||||
},
|
||||
data,
|
||||
jar,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
@@ -293,12 +297,12 @@ describe('Controllers', () => {
|
||||
it('email interstitial should still apply if empty email entered and requireEmailAddress is enabled', async () => {
|
||||
const { response: res } = await request.post(`${nconf.get('url')}/register/complete`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
email: '',
|
||||
},
|
||||
});
|
||||
@@ -504,6 +508,7 @@ describe('Controllers', () => {
|
||||
async function abortInterstitial() {
|
||||
await request.post(`${nconf.get('url')}/register/abort`, {
|
||||
jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
@@ -515,12 +520,12 @@ describe('Controllers', () => {
|
||||
|
||||
const { response } = await request.post(`${nconf.get('url')}/register/complete`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
email: `${utils.generateUUID().slice(0, 10)}@example.org`,
|
||||
gdpr_agree_data: 'on',
|
||||
gdpr_agree_email: 'on',
|
||||
@@ -535,7 +540,8 @@ describe('Controllers', () => {
|
||||
it('should allow access to regular resources after an email is entered, even if unconfirmed', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/recent`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
@@ -548,8 +554,8 @@ describe('Controllers', () => {
|
||||
await privileges.categories.give(['groups:read'], cid, ['verified-users']);
|
||||
const { response } = await request.get(`${nconf.get('url')}/category/${cid}/${slugify(name)}`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 307);
|
||||
@@ -564,8 +570,8 @@ describe('Controllers', () => {
|
||||
await privileges.categories.give(['groups:topics:read'], cid, 'verified-users');
|
||||
const { response } = await request.get(`${nconf.get('url')}/category/${cid}/${slugify(name)}`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
@@ -575,8 +581,8 @@ describe('Controllers', () => {
|
||||
const { topicData } = await topics.post({ uid, cid, title, content: utils.generateUUID() });
|
||||
const { response: res2 } = await request.get(`${nconf.get('url')}/topic/${topicData.tid}/${slugify(title)}`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.strictEqual(res2.statusCode, 307);
|
||||
assert.strictEqual(res2.headers.location, `${nconf.get('relative_path')}/register/complete`);
|
||||
@@ -601,12 +607,12 @@ describe('Controllers', () => {
|
||||
it('registration should succeed once gdpr prompts are agreed to', async () => {
|
||||
const { response } = await request.post(`${nconf.get('url')}/register/complete`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
gdpr_agree_data: 'on',
|
||||
gdpr_agree_email: 'on',
|
||||
},
|
||||
@@ -632,15 +638,15 @@ describe('Controllers', () => {
|
||||
it('should terminate the session and send user back to index if interstitials remain', async () => {
|
||||
const { response } = await request.post(`${nconf.get('url')}/register/abort`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 302);
|
||||
assert.strictEqual(response.headers['set-cookie'], `express.sid=; Path=${nconf.get('relative_path') || '/'}; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`);
|
||||
assert.strictEqual(response.headers['set-cookie'][0], `express.sid=; Path=${nconf.get('relative_path') || '/'}; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`);
|
||||
assert.strictEqual(response.headers.location, `${nconf.get('relative_path')}/`);
|
||||
});
|
||||
|
||||
@@ -648,12 +654,12 @@ describe('Controllers', () => {
|
||||
// Submit GDPR consent
|
||||
await request.post(`${nconf.get('url')}/register/complete`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
gdpr_agree_data: 'on',
|
||||
gdpr_agree_email: 'on',
|
||||
},
|
||||
@@ -664,8 +670,8 @@ describe('Controllers', () => {
|
||||
|
||||
const { response } = await request.post(`${nconf.get('url')}/register/abort`, {
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
@@ -688,14 +694,14 @@ describe('Controllers', () => {
|
||||
|
||||
it('should return 404 if meta.config.termsOfUse is empty', async () => {
|
||||
meta.config.termsOfUse = '';
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/tos`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/tos`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
|
||||
|
||||
it('should error if guests do not have search privilege', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/users?query=bar§ion=sort-posts`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/users?query=bar§ion=sort-posts`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 500);
|
||||
assert(body);
|
||||
assert.equal(body.error, '[[error:no-privileges]]');
|
||||
@@ -743,7 +749,7 @@ describe('Controllers', () => {
|
||||
description: 'Foobar!',
|
||||
hidden: 1,
|
||||
});
|
||||
const { response } = await request.get(`${nconf.get('url')}/groups/hidden-group/members`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/groups/hidden-group/members`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -764,6 +770,7 @@ describe('Controllers', () => {
|
||||
it('should fail to revoke session with missing uuid', async () => {
|
||||
const { response } = await request.del(`${nconf.get('url')}/api/user/revokeme/session`, {
|
||||
jar: jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -774,6 +781,7 @@ describe('Controllers', () => {
|
||||
it('should fail if user doesn\'t exist', async () => {
|
||||
const { response, body } = await request.del(`${nconf.get('url')}/api/v3/users/doesnotexist/sessions/1112233`, {
|
||||
jar: jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -906,12 +914,12 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should return 503 in maintenance mode', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/recent`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/recent`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 503);
|
||||
});
|
||||
|
||||
it('should return 503 in maintenance mode', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/recent`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/recent`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 503);
|
||||
assert(body);
|
||||
});
|
||||
@@ -948,7 +956,7 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 if uid is not a number', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/uid/test`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/uid/test`, { jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -967,7 +975,7 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 if user does not exist', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/uid/123123`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/uid/123123`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -1001,12 +1009,12 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 401 if user is not logged in', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/admin`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/admin`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 401);
|
||||
});
|
||||
|
||||
it('should 403 if user is not admin', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/admin`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/admin`, { jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 403);
|
||||
});
|
||||
|
||||
@@ -1017,7 +1025,7 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 401 if not logged in', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/admin`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 401);
|
||||
assert(body);
|
||||
});
|
||||
@@ -1164,7 +1172,7 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 if user does not exist', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/email/doesnotexist`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/email/doesnotexist`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
@@ -1182,14 +1190,18 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should NOT load user by email (by default)', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/email/foo@test.com`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/email/foo@test.com`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should load user by email if user has elected to show their email', async () => {
|
||||
await user.setSetting(fooUid, 'showemail', 1);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/email/foo@test.com`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/email/foo@test.com`, {
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body);
|
||||
await user.setSetting(fooUid, 'showemail', 0);
|
||||
@@ -1198,7 +1210,7 @@ describe('Controllers', () => {
|
||||
it('should return 401 if user does not have view:users privilege', async () => {
|
||||
await privileges.global.rescind(['groups:view:users'], 'guests');
|
||||
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/foo`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/foo`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 401);
|
||||
assert.deepEqual(body, {
|
||||
response: {},
|
||||
@@ -1213,9 +1225,9 @@ describe('Controllers', () => {
|
||||
it('should return false if user can not edit user', async () => {
|
||||
await user.create({ username: 'regularJoe', password: 'barbar' });
|
||||
const { jar } = await helpers.loginUser('regularJoe', 'barbar');
|
||||
let { response } = await request.get(`${nconf.get('url')}/api/user/foo/info`, { jar });
|
||||
let { response } = await request.get(`${nconf.get('url')}/api/user/foo/info`, { jar: jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 403);
|
||||
({ response } = await request.get(`${nconf.get('url')}/api/user/foo/edit`, { jar }));
|
||||
({ response } = await request.get(`${nconf.get('url')}/api/user/foo/edit`, { jar: jar, validateStatus: null }));
|
||||
assert.equal(response.statusCode, 403);
|
||||
});
|
||||
|
||||
@@ -1231,7 +1243,7 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 if user does not exist', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/doesnotexist`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/doesnotexist`, { jar: jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -1306,7 +1318,7 @@ describe('Controllers', () => {
|
||||
it('should 404 if user does not exist', async () => {
|
||||
await groups.join('administrators', fooUid);
|
||||
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/doesnotexist/edit`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/doesnotexist/edit`, { jar: jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
await groups.leave('administrators', fooUid);
|
||||
});
|
||||
@@ -1317,13 +1329,16 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should render edit/email', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/foo/edit/email`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/foo/edit/email`, {
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert.strictEqual(body, '/register/complete');
|
||||
|
||||
await request.post(`${nconf.get('url')}/register/abort`, {
|
||||
jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -1374,13 +1389,13 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should 404 for invalid pid', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/post/fail`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/post/fail`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should 403 if user does not have read privilege', async () => {
|
||||
await privileges.categories.rescind(['groups:topics:read'], category.cid, 'registered-users');
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/post/${pid}`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/post/${pid}`, { jar: jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 403);
|
||||
await privileges.categories.give(['groups:topics:read'], category.cid, 'registered-users');
|
||||
});
|
||||
@@ -1431,13 +1446,13 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should handle malformed uri ', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/user/a%AFc`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/user/a%AFc`, { validateStatus: null });
|
||||
assert(body);
|
||||
assert.equal(response.statusCode, 400);
|
||||
});
|
||||
|
||||
it('should handle malformed uri in api', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/a%AFc`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/a%AFc`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 400);
|
||||
assert.equal(body.error, '[[global:400.title]]');
|
||||
});
|
||||
@@ -1452,7 +1467,7 @@ describe('Controllers', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { response } = await request.get(`${nconf.get('url')}/users`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/users`, { validateStatus: null });
|
||||
plugins.loadedHooks['filter:router.page'] = [];
|
||||
assert.equal(response.statusCode, 403);
|
||||
});
|
||||
@@ -1466,7 +1481,7 @@ describe('Controllers', () => {
|
||||
next(err);
|
||||
},
|
||||
});
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/users`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/users`, { validateStatus: null });
|
||||
plugins.loadedHooks['filter:router.page'] = [];
|
||||
assert.equal(response.statusCode, 403);
|
||||
assert.equal(body, 'blacklist error message');
|
||||
@@ -1513,7 +1528,7 @@ describe('Controllers', () => {
|
||||
next(err);
|
||||
},
|
||||
});
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/users`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/users`, { validateStatus: null });
|
||||
plugins.loadedHooks['filter:router.page'] = [];
|
||||
assert.equal(response.statusCode, 500);
|
||||
assert(body);
|
||||
@@ -1527,31 +1542,31 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should return 404 if cid is not a number', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/fail`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/fail`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should return 404 if topic index is not a number', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/${category.slug}/invalidtopicindex`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/${category.slug}/invalidtopicindex`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should 404 if category does not exist', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/123123`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/category/123123`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should 404 if category is disabled', async () => {
|
||||
const category = await categories.create({ name: 'disabled' });
|
||||
await categories.setCategoryField(category.cid, 'disabled', 1);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/category/${category.slug}`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/category/${category.slug}`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should return 401 if not allowed to read', async () => {
|
||||
const category = await categories.create({ name: 'hidden' });
|
||||
await privileges.categories.rescind(['groups:read'], category.cid, 'guests');
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/category/${category.slug}`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/category/${category.slug}`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 401);
|
||||
});
|
||||
|
||||
@@ -1563,7 +1578,7 @@ describe('Controllers', () => {
|
||||
|
||||
it('should 404 if page is not found', async () => {
|
||||
await user.setSetting(fooUid, 'usePagination', 1);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/category/${category.slug}?page=100`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/category/${category.slug}?page=100`, { jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -1668,23 +1683,23 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should load unread page', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/unread`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/unread`, { jar: jar });
|
||||
assert.equal(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should 404 if filter is invalid', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/unread/doesnotexist`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/unread/doesnotexist`, { jar: jar, validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should return total unread count', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/unread/total?filter=new`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/unread/total?filter=new`, { jar: jar });
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(body, 0);
|
||||
});
|
||||
|
||||
it('should redirect if page is out of bounds', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/unread?page=-1`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/unread?page=-1`, { jar: jar });
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(response.headers['x-redirect'], '/unread?page=1');
|
||||
assert.equal(body, '/unread?page=1');
|
||||
@@ -1693,7 +1708,7 @@ describe('Controllers', () => {
|
||||
|
||||
describe('admin middlewares', () => {
|
||||
it('should redirect to login', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/admin/advanced/database`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/admin/advanced/database`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 401);
|
||||
});
|
||||
|
||||
@@ -1751,48 +1766,51 @@ describe('Controllers', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.statusCode, 400);
|
||||
result = await request.post(`${nconf.get('url')}/compose`, {
|
||||
body: {
|
||||
data: {
|
||||
tid: tid,
|
||||
},
|
||||
jar: jar,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(result.response.statusCode, 400);
|
||||
});
|
||||
|
||||
it('should create a new topic and reply by composer route', async () => {
|
||||
const data = {
|
||||
cid: cid,
|
||||
title: 'no js is good',
|
||||
content: 'a topic with noscript',
|
||||
};
|
||||
let result = await request.post(`${nconf.get('url')}/compose`, {
|
||||
body: {
|
||||
cid: cid,
|
||||
title: 'no js is good',
|
||||
content: 'a topic with noscript',
|
||||
},
|
||||
data: data,
|
||||
jar: jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.statusCode, 302);
|
||||
result = await request.post(`${nconf.get('url')}/compose`, {
|
||||
body: {
|
||||
data: {
|
||||
tid: tid,
|
||||
content: 'a new reply',
|
||||
},
|
||||
jar: jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(result.response.statusCode, 302);
|
||||
});
|
||||
@@ -1800,37 +1818,38 @@ describe('Controllers', () => {
|
||||
it('should create a new topic and reply by composer route as a guest', async () => {
|
||||
const jar = request.jar();
|
||||
const csrf_token = await helpers.getCsrfToken(jar);
|
||||
const data = {
|
||||
cid: cid,
|
||||
title: 'no js is good',
|
||||
content: 'a topic with noscript',
|
||||
handle: 'guest1',
|
||||
};
|
||||
|
||||
await privileges.categories.give(['groups:topics:create', 'groups:topics:reply'], cid, 'guests');
|
||||
|
||||
const result = await helpers.request('post', `/compose`, {
|
||||
body: {
|
||||
cid: cid,
|
||||
title: 'no js is good',
|
||||
content: 'a topic with noscript',
|
||||
handle: 'guest1',
|
||||
},
|
||||
data: data,
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.strictEqual(result.response.statusCode, 302);
|
||||
|
||||
const replyResult = await helpers.request('post', `/compose`, {
|
||||
body: {
|
||||
data: {
|
||||
tid: tid,
|
||||
content: 'a new reply',
|
||||
handle: 'guest2',
|
||||
},
|
||||
jar,
|
||||
maxRedirect: 0,
|
||||
redirect: 'manual',
|
||||
maxRedirects: 0,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(replyResult.response.statusCode, 302);
|
||||
await privileges.categories.rescind(['groups:topics:post', 'groups:topics:reply'], cid, 'guests');
|
||||
@@ -1840,7 +1859,7 @@ describe('Controllers', () => {
|
||||
describe('test routes', () => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
it('should load debug route', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/debug/test`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/debug/test`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
@@ -1858,7 +1877,7 @@ describe('Controllers', () => {
|
||||
});
|
||||
|
||||
it('should load 404 for invalid type', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/debug/spec/doesnotexist`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/debug/spec/doesnotexist`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
|
||||
@@ -50,19 +50,19 @@ describe('feeds', () => {
|
||||
];
|
||||
for (const url of feedUrls) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { response } = await request.get(url);
|
||||
const { response } = await request.get(url, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
}
|
||||
meta.config['feeds:disableRSS'] = 0;
|
||||
});
|
||||
|
||||
it('should 404 if topic does not exist', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/topic/${1000}.rss`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/topic/${1000}.rss`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should 404 if category id is not a number', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/category/invalid.rss`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/category/invalid.rss`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('feeds', () => {
|
||||
});
|
||||
|
||||
it('should 404 if user is not found', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/user/doesnotexist/topics.rss`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/user/doesnotexist/topics.rss`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
|
||||
@@ -894,7 +894,7 @@ describe('Flags', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrfToken,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
type: 'post',
|
||||
id: pid,
|
||||
reason: 'foobar',
|
||||
@@ -917,7 +917,7 @@ describe('Flags', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrfToken,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
type: 'post',
|
||||
id: postData.pid,
|
||||
reason: '"<script>alert(\'ok\');</script>',
|
||||
@@ -948,11 +948,12 @@ describe('Flags', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrfToken,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
type: 'post',
|
||||
id: result.postData.pid,
|
||||
reason: 'foobar',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.strictEqual(response.statusCode, 403);
|
||||
|
||||
@@ -976,7 +977,7 @@ describe('Flags', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrfToken,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
state: 'wip',
|
||||
},
|
||||
});
|
||||
@@ -1008,7 +1009,7 @@ describe('Flags', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrfToken,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
note: 'lorem ipsum dolor sit amet',
|
||||
datetime: 1626446956652,
|
||||
},
|
||||
@@ -1055,7 +1056,7 @@ describe('Flags', () => {
|
||||
before(async () => {
|
||||
uid = await User.create({ username: 'flags-access-control', password: 'abcdef' });
|
||||
({ jar, csrf_token } = await helpers.loginUser('flags-access-control', 'abcdef'));
|
||||
console.log('cs', csrfToken);
|
||||
|
||||
flaggerUid = await User.create({ username: 'flags-access-control-flagger', password: 'abcdef' });
|
||||
});
|
||||
|
||||
@@ -1078,6 +1079,7 @@ describe('Flags', () => {
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
};
|
||||
requests = new Set([
|
||||
{
|
||||
@@ -1089,7 +1091,7 @@ describe('Flags', () => {
|
||||
...commonOpts,
|
||||
method: 'put',
|
||||
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
|
||||
body: {
|
||||
data: {
|
||||
state: 'wip',
|
||||
},
|
||||
},
|
||||
@@ -1097,7 +1099,7 @@ describe('Flags', () => {
|
||||
...commonOpts,
|
||||
method: 'post',
|
||||
uri: `${nconf.get('url')}/api/v3/flags/${flagId}/notes`,
|
||||
body: {
|
||||
data: {
|
||||
note: 'test note',
|
||||
datetime: noteTime,
|
||||
},
|
||||
|
||||
@@ -28,7 +28,9 @@ helpers.request = async function (method, uri, options = {}) {
|
||||
if (csrf_token) {
|
||||
options.headers['x-csrf-token'] = csrf_token;
|
||||
}
|
||||
return await request[lowercaseMethod](`${nconf.get('url')}${uri}`, options);
|
||||
options.validateStatus = null;
|
||||
const { response, body } = await request[lowercaseMethod](`${nconf.get('url')}${uri}`, options);
|
||||
return { response, body };
|
||||
};
|
||||
|
||||
helpers.loginUser = async (username, password, payload = {}) => {
|
||||
@@ -37,8 +39,9 @@ helpers.loginUser = async (username, password, payload = {}) => {
|
||||
|
||||
const csrf_token = await helpers.getCsrfToken(jar);
|
||||
const { response, body } = await request.post(`${nconf.get('url')}/login`, {
|
||||
body: data,
|
||||
data,
|
||||
jar: jar,
|
||||
validateStatus: () => true,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -50,8 +53,9 @@ helpers.loginUser = async (username, password, payload = {}) => {
|
||||
helpers.logoutUser = async function (jar) {
|
||||
const csrf_token = await helpers.getCsrfToken(jar);
|
||||
const { response, body } = await request.post(`${nconf.get('url')}/logout`, {
|
||||
body: {},
|
||||
data: {},
|
||||
jar,
|
||||
validateStatus: () => true,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -61,7 +65,9 @@ helpers.logoutUser = async function (jar) {
|
||||
|
||||
helpers.connectSocketIO = function (res, csrf_token) {
|
||||
const io = require('socket.io-client');
|
||||
const cookie = res.headers['set-cookie'];
|
||||
let cookies = res.headers['set-cookie'];
|
||||
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
|
||||
const cookie = cookies[0];
|
||||
const socket = io(nconf.get('base_url'), {
|
||||
path: `${nconf.get('relative_path')}/socket.io`,
|
||||
extraHeaders: {
|
||||
@@ -90,35 +96,26 @@ helpers.connectSocketIO = function (res, csrf_token) {
|
||||
};
|
||||
|
||||
helpers.uploadFile = async function (uploadEndPoint, filePath, data, jar, csrf_token) {
|
||||
const mime = require('mime');
|
||||
const FormData = require('form-data');
|
||||
const form = new FormData();
|
||||
const file = await fs.promises.readFile(filePath);
|
||||
const blob = new Blob([file], { type: mime.getType(filePath) });
|
||||
|
||||
form.append('files', blob, path.basename(filePath));
|
||||
|
||||
form.append('files', fs.createReadStream(filePath), path.basename(filePath));
|
||||
if (data && data.params) {
|
||||
form.append('params', data.params);
|
||||
}
|
||||
|
||||
const response = await fetch(uploadEndPoint, {
|
||||
method: 'post',
|
||||
body: form,
|
||||
const { response, body } = await request.post(uploadEndPoint, {
|
||||
data: form,
|
||||
jar: jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
cookie: await jar.getCookieString(uploadEndPoint),
|
||||
...form.getHeaders(),
|
||||
},
|
||||
});
|
||||
const body = await response.json();
|
||||
return {
|
||||
body,
|
||||
response: {
|
||||
status: response.status,
|
||||
statusCode: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
},
|
||||
};
|
||||
if (response.status !== 200) {
|
||||
winston.error(JSON.stringify(data));
|
||||
}
|
||||
return { response, body };
|
||||
};
|
||||
|
||||
helpers.registerUser = async function (data) {
|
||||
@@ -130,8 +127,9 @@ helpers.registerUser = async function (data) {
|
||||
}
|
||||
|
||||
const { response, body } = await request.post(`${nconf.get('url')}/register`, {
|
||||
body: data,
|
||||
data,
|
||||
jar,
|
||||
validateStatus: () => true,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -167,23 +165,25 @@ helpers.copyFile = function (source, target, callback) {
|
||||
helpers.invite = async function (data, uid, jar, csrf_token) {
|
||||
return await request.post(`${nconf.get('url')}/api/v3/users/${uid}/invites`, {
|
||||
jar: jar,
|
||||
body: data,
|
||||
data: data,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
};
|
||||
|
||||
helpers.createFolder = async function (path, folderName, jar, csrf_token) {
|
||||
return await request.put(`${nconf.get('url')}/api/v3/files/folder`, {
|
||||
jar,
|
||||
body: {
|
||||
data: {
|
||||
path,
|
||||
folderName,
|
||||
},
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ describe('Messaging Library', () => {
|
||||
|
||||
const callv3API = async (method, path, body, user) => {
|
||||
const options = {
|
||||
body,
|
||||
data: body,
|
||||
jar: mocks.users[user].jar,
|
||||
validateStatus: null,
|
||||
};
|
||||
|
||||
if (method !== 'get') {
|
||||
@@ -301,7 +302,7 @@ describe('Messaging Library', () => {
|
||||
const receiver = await User.create({ username: 'receiver' });
|
||||
const { body } = await request.post(`${nconf.get('url')}/api/v3/chats`, {
|
||||
jar: senderJar,
|
||||
body: {
|
||||
data: {
|
||||
uids: [receiver],
|
||||
},
|
||||
headers: {
|
||||
@@ -761,21 +762,29 @@ describe('Messaging Library', () => {
|
||||
describe('controller', () => {
|
||||
it('should 404 if chat is disabled', async () => {
|
||||
meta.config.disableChat = 1;
|
||||
const { response } = await request.get(`${nconf.get('url')}/user/baz/chats`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/user/baz/chats`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should 401 for guest with not-authorised status code', async () => {
|
||||
meta.config.disableChat = 0;
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/baz/chats`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/baz/chats`, {
|
||||
resolveWithFullResponse: true,
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 401);
|
||||
assert.equal(body.status.code, 'not-authorised');
|
||||
});
|
||||
|
||||
it('should 404 for non-existent user', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/user/doesntexist/chats`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/user/doesntexist/chats`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
});
|
||||
@@ -787,7 +796,10 @@ describe('Messaging Library', () => {
|
||||
});
|
||||
|
||||
it('should return chats page data', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats`, {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert(Array.isArray(body.rooms));
|
||||
@@ -796,7 +808,10 @@ describe('Messaging Library', () => {
|
||||
});
|
||||
|
||||
it('should return room data', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats/${roomId}`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats/${roomId}`, {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(body.roomId, roomId);
|
||||
@@ -804,7 +819,10 @@ describe('Messaging Library', () => {
|
||||
});
|
||||
|
||||
it('should redirect to chats page', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/chats`, { jar });
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/api/chats`, {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.equal(response.headers['x-redirect'], '/user/herp/chats');
|
||||
@@ -813,7 +831,10 @@ describe('Messaging Library', () => {
|
||||
|
||||
it('should return 404 if user is not in room', async () => {
|
||||
const data = await helpers.loginUser('baz', 'quuxquux');
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, { jar: data.jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, {
|
||||
validateStatus: null,
|
||||
jar: data.jar,
|
||||
});
|
||||
|
||||
assert.equal(response.statusCode, 404);
|
||||
});
|
||||
|
||||
11
test/meta.js
11
test/meta.js
@@ -493,6 +493,8 @@ describe('meta', () => {
|
||||
it('Access-Control-Allow-Origin header should be empty', async () => {
|
||||
const jar = request.jar();
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
|
||||
data: {},
|
||||
validateStatus: null,
|
||||
jar: jar,
|
||||
});
|
||||
|
||||
@@ -504,10 +506,12 @@ describe('meta', () => {
|
||||
const oldValue = meta.config['access-control-allow-origin'];
|
||||
meta.config['access-control-allow-origin'] = 'test.com, mydomain.com';
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
|
||||
data: { },
|
||||
jar: jar,
|
||||
headers: {
|
||||
origin: 'mydomain.com',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
|
||||
@@ -524,6 +528,7 @@ describe('meta', () => {
|
||||
headers: {
|
||||
origin: 'notallowed.com',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.headers['access-control-allow-origin'], undefined);
|
||||
meta.config['access-control-allow-origin'] = oldValue;
|
||||
@@ -534,10 +539,12 @@ describe('meta', () => {
|
||||
const oldValue = meta.config['access-control-allow-origin-regex'];
|
||||
meta.config['access-control-allow-origin-regex'] = 'match\\.this\\..+\\.domain.com, mydomain\\.com';
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
|
||||
data: {},
|
||||
jar: jar,
|
||||
headers: {
|
||||
origin: 'match.this.anything123.domain.com',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.equal(response.headers['access-control-allow-origin'], 'match.this.anything123.domain.com');
|
||||
@@ -549,10 +556,12 @@ describe('meta', () => {
|
||||
const oldValue = meta.config['access-control-allow-origin-regex'];
|
||||
meta.config['access-control-allow-origin-regex'] = 'match\\.this\\..+\\.domain.com, mydomain\\.com';
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
|
||||
data: {},
|
||||
jar: jar,
|
||||
headers: {
|
||||
origin: 'notallowed.com',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.headers['access-control-allow-origin'], undefined);
|
||||
meta.config['access-control-allow-origin-regex'] = oldValue;
|
||||
@@ -563,10 +572,12 @@ describe('meta', () => {
|
||||
const oldValue = meta.config['access-control-allow-origin-regex'];
|
||||
meta.config['access-control-allow-origin-regex'] = '[match\\.this\\..+\\.domain.com, mydomain\\.com';
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
|
||||
data: { },
|
||||
jar: jar,
|
||||
headers: {
|
||||
origin: 'mydomain.com',
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
|
||||
meta.config['access-control-allow-origin-regex'] = oldValue;
|
||||
|
||||
@@ -116,7 +116,9 @@ describe('Middlewares', () => {
|
||||
});
|
||||
|
||||
it('should be absent on non-existent routes, for guests', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
assert(!Object.keys(response.headers).includes('cache-control'));
|
||||
@@ -124,6 +126,7 @@ describe('Middlewares', () => {
|
||||
|
||||
it('should be set to "private" on non-existent routes, for logged in users', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`, {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
headers: {
|
||||
accept: 'text/html',
|
||||
@@ -136,21 +139,28 @@ describe('Middlewares', () => {
|
||||
});
|
||||
|
||||
it('should be absent on regular routes, for guests', async () => {
|
||||
const { response } = await request.get(nconf.get('url'));
|
||||
const { response } = await request.get(nconf.get('url'), {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(!Object.keys(response.headers).includes('cache-control'));
|
||||
});
|
||||
|
||||
it('should be absent on api routes, for guests', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api`);
|
||||
const { response } = await request.get(`${nconf.get('url')}/api`, {
|
||||
validateStatus: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(!Object.keys(response.headers).includes('cache-control'));
|
||||
});
|
||||
|
||||
it('should be set to "private" on regular routes, for logged-in users', async () => {
|
||||
const { response } = await request.get(nconf.get('url'), { jar });
|
||||
const { response } = await request.get(nconf.get('url'), {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(Object.keys(response.headers).includes('cache-control'));
|
||||
@@ -158,7 +168,10 @@ describe('Middlewares', () => {
|
||||
});
|
||||
|
||||
it('should be set to "private" on api routes, for logged-in users', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api`, {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(Object.keys(response.headers).includes('cache-control'));
|
||||
@@ -166,7 +179,10 @@ describe('Middlewares', () => {
|
||||
});
|
||||
|
||||
it('should be set to "private" on apiv3 routes, for logged-in users', async () => {
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/v3/users/${uid}`, { jar });
|
||||
const { response } = await request.get(`${nconf.get('url')}/api/v3/users/${uid}`, {
|
||||
validateStatus: null,
|
||||
jar,
|
||||
});
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(Object.keys(response.headers).includes('cache-control'));
|
||||
|
||||
@@ -292,14 +292,14 @@ describe('Plugins', () => {
|
||||
|
||||
describe('static assets', () => {
|
||||
it('should 404 if resource does not exist', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/plugins/doesnotexist/should404.tpl`);
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/plugins/doesnotexist/should404.tpl`, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
|
||||
it('should 404 if resource does not exist', async () => {
|
||||
const url = `${nconf.get('url')}/plugins/nodebb-plugin-dbsearch/dbsearch/templates/admin/plugins/should404.tpl`;
|
||||
const { response, body } = await request.get(url);
|
||||
const { response, body } = await request.get(url, { validateStatus: null });
|
||||
assert.equal(response.statusCode, 404);
|
||||
assert(body);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
|
||||
const nconf = require('nconf');
|
||||
const path = require('path');
|
||||
@@ -33,22 +34,48 @@ describe('Post\'s', () => {
|
||||
let topicData;
|
||||
let cid;
|
||||
|
||||
before(async () => {
|
||||
voterUid = await user.create({ username: 'upvoter' });
|
||||
voteeUid = await user.create({ username: 'upvotee' });
|
||||
globalModUid = await user.create({ username: 'globalmod', password: 'globalmodpwd' });
|
||||
({ cid } = await categories.create({
|
||||
name: 'Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
}));
|
||||
before((done) => {
|
||||
async.series({
|
||||
voterUid: function (next) {
|
||||
user.create({ username: 'upvoter' }, next);
|
||||
},
|
||||
voteeUid: function (next) {
|
||||
user.create({ username: 'upvotee' }, next);
|
||||
},
|
||||
globalModUid: function (next) {
|
||||
user.create({ username: 'globalmod', password: 'globalmodpwd' }, next);
|
||||
},
|
||||
category: function (next) {
|
||||
categories.create({
|
||||
name: 'Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
}, next);
|
||||
},
|
||||
}, (err, results) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
({ topicData, postData } = await topics.post({
|
||||
uid: voteeUid,
|
||||
cid: cid,
|
||||
title: 'Test Topic Title',
|
||||
content: 'The content of test topic',
|
||||
}));
|
||||
await groups.join('Global Moderators', globalModUid);
|
||||
voterUid = results.voterUid;
|
||||
voteeUid = results.voteeUid;
|
||||
globalModUid = results.globalModUid;
|
||||
cid = results.category.cid;
|
||||
|
||||
topics.post({
|
||||
uid: results.voteeUid,
|
||||
cid: results.category.cid,
|
||||
title: 'Test Topic Title',
|
||||
content: 'The content of test topic',
|
||||
}, (err, data) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
postData = data.postData;
|
||||
topicData = data.topicData;
|
||||
|
||||
groups.join('Global Moderators', globalModUid, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update category teaser properly', async () => {
|
||||
@@ -1031,10 +1058,20 @@ describe('Post\'s', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept queued posts and submit', async () => {
|
||||
const ids = await db.getSortedSetRange('post:queue', 0, -1);
|
||||
await socketPosts.accept({ uid: globalModUid }, { id: ids[0] });
|
||||
await socketPosts.accept({ uid: globalModUid }, { id: ids[1] });
|
||||
it('should accept queued posts and submit', (done) => {
|
||||
let ids;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRange('post:queue', 0, -1, next);
|
||||
},
|
||||
function (_ids, next) {
|
||||
ids = _ids;
|
||||
socketPosts.accept({ uid: globalModUid }, { id: ids[0] }, next);
|
||||
},
|
||||
function (next) {
|
||||
socketPosts.accept({ uid: globalModUid }, { id: ids[1] }, next);
|
||||
},
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should not crash if id does not exist', (done) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const nconf = require('nconf');
|
||||
const async = require('async');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const db = require('../mocks/databasemock');
|
||||
@@ -74,15 +75,24 @@ describe('upload methods', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove an image if it is edited out of the post', async () => {
|
||||
await posts.edit({
|
||||
pid: pid,
|
||||
uid,
|
||||
content: 'here is an image [alt text](/assets/uploads/files/abracadabra.png)... AND NO MORE!',
|
||||
it('should remove an image if it is edited out of the post', (done) => {
|
||||
async.series([
|
||||
function (next) {
|
||||
posts.edit({
|
||||
pid: pid,
|
||||
uid,
|
||||
content: 'here is an image [alt text](/assets/uploads/files/abracadabra.png)... AND NO MORE!',
|
||||
}, next);
|
||||
},
|
||||
async.apply(posts.uploads.sync, pid),
|
||||
], (err) => {
|
||||
assert.ifError(err);
|
||||
db.sortedSetCard(`post:${pid}:uploads`, (err, length) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(1, length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
await posts.uploads.sync(pid);
|
||||
const length = await db.sortedSetCard(`post:${pid}:uploads`);
|
||||
assert.strictEqual(1, length);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,52 +127,85 @@ describe('upload methods', () => {
|
||||
});
|
||||
|
||||
describe('.associate()', () => {
|
||||
it('should add an image to the post\'s maintained list of uploads', async () => {
|
||||
await posts.uploads.associate(pid, 'files/whoa.gif');
|
||||
const uploads = await posts.uploads.list(pid);
|
||||
assert.strictEqual(2, uploads.length);
|
||||
assert.strictEqual(true, uploads.includes('files/whoa.gif'));
|
||||
it('should add an image to the post\'s maintained list of uploads', (done) => {
|
||||
async.waterfall([
|
||||
async.apply(posts.uploads.associate, pid, 'files/whoa.gif'),
|
||||
async.apply(posts.uploads.list, pid),
|
||||
], (err, uploads) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(2, uploads.length);
|
||||
assert.strictEqual(true, uploads.includes('files/whoa.gif'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow arrays to be passed in', async () => {
|
||||
await posts.uploads.associate(pid, ['files/amazeballs.jpg', 'files/wut.txt']);
|
||||
const uploads = await posts.uploads.list(pid);
|
||||
assert.strictEqual(4, uploads.length);
|
||||
assert.strictEqual(true, uploads.includes('files/amazeballs.jpg'));
|
||||
assert.strictEqual(true, uploads.includes('files/wut.txt'));
|
||||
it('should allow arrays to be passed in', (done) => {
|
||||
async.waterfall([
|
||||
async.apply(posts.uploads.associate, pid, ['files/amazeballs.jpg', 'files/wut.txt']),
|
||||
async.apply(posts.uploads.list, pid),
|
||||
], (err, uploads) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(4, uploads.length);
|
||||
assert.strictEqual(true, uploads.includes('files/amazeballs.jpg'));
|
||||
assert.strictEqual(true, uploads.includes('files/wut.txt'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should save a reverse association of md5sum to pid', async () => {
|
||||
it('should save a reverse association of md5sum to pid', (done) => {
|
||||
const md5 = filename => crypto.createHash('md5').update(filename).digest('hex');
|
||||
await posts.uploads.associate(pid, ['files/test.bmp']);
|
||||
const pids = await db.getSortedSetRange(`upload:${md5('files/test.bmp')}:pids`, 0, -1);
|
||||
assert.strictEqual(true, Array.isArray(pids));
|
||||
assert.strictEqual(true, pids.length > 0);
|
||||
assert.equal(pid, pids[0]);
|
||||
|
||||
async.waterfall([
|
||||
async.apply(posts.uploads.associate, pid, ['files/test.bmp']),
|
||||
function (next) {
|
||||
db.getSortedSetRange(`upload:${md5('files/test.bmp')}:pids`, 0, -1, next);
|
||||
},
|
||||
], (err, pids) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(true, Array.isArray(pids));
|
||||
assert.strictEqual(true, pids.length > 0);
|
||||
assert.equal(pid, pids[0]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not associate a file that does not exist on the local disk', async () => {
|
||||
await posts.uploads.associate(pid, ['files/nonexistant.xls']);
|
||||
const uploads = await posts.uploads.list(pid);
|
||||
assert.strictEqual(uploads.length, 5);
|
||||
assert.strictEqual(false, uploads.includes('files/nonexistant.xls'));
|
||||
it('should not associate a file that does not exist on the local disk', (done) => {
|
||||
async.waterfall([
|
||||
async.apply(posts.uploads.associate, pid, ['files/nonexistant.xls']),
|
||||
async.apply(posts.uploads.list, pid),
|
||||
], (err, uploads) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(uploads.length, 5);
|
||||
assert.strictEqual(false, uploads.includes('files/nonexistant.xls'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.dissociate()', () => {
|
||||
it('should remove an image from the post\'s maintained list of uploads', async () => {
|
||||
await posts.uploads.dissociate(pid, 'files/whoa.gif');
|
||||
const uploads = await posts.uploads.list(pid);
|
||||
assert.strictEqual(4, uploads.length);
|
||||
assert.strictEqual(false, uploads.includes('files/whoa.gif'));
|
||||
it('should remove an image from the post\'s maintained list of uploads', (done) => {
|
||||
async.waterfall([
|
||||
async.apply(posts.uploads.dissociate, pid, 'files/whoa.gif'),
|
||||
async.apply(posts.uploads.list, pid),
|
||||
], (err, uploads) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(4, uploads.length);
|
||||
assert.strictEqual(false, uploads.includes('files/whoa.gif'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow arrays to be passed in', async () => {
|
||||
await posts.uploads.dissociate(pid, ['files/amazeballs.jpg', 'files/wut.txt']);
|
||||
const uploads = await posts.uploads.list(pid);
|
||||
assert.strictEqual(2, uploads.length);
|
||||
assert.strictEqual(false, uploads.includes('files/amazeballs.jpg'));
|
||||
assert.strictEqual(false, uploads.includes('files/wut.txt'));
|
||||
it('should allow arrays to be passed in', (done) => {
|
||||
async.waterfall([
|
||||
async.apply(posts.uploads.dissociate, pid, ['files/amazeballs.jpg', 'files/wut.txt']),
|
||||
async.apply(posts.uploads.list, pid),
|
||||
], (err, uploads) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(2, uploads.length);
|
||||
assert.strictEqual(false, uploads.includes('files/amazeballs.jpg'));
|
||||
assert.strictEqual(false, uploads.includes('files/wut.txt'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the image\'s user association, if present', async () => {
|
||||
@@ -354,14 +397,21 @@ describe('post uploads management', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should automatically sync uploads on post edit', async () => {
|
||||
await posts.edit({
|
||||
pid: reply.pid,
|
||||
uid,
|
||||
content: 'no uploads',
|
||||
it('should automatically sync uploads on post edit', (done) => {
|
||||
async.waterfall([
|
||||
async.apply(posts.edit, {
|
||||
pid: reply.pid,
|
||||
uid,
|
||||
content: 'no uploads',
|
||||
}),
|
||||
function (postData, next) {
|
||||
posts.uploads.list(reply.pid, next);
|
||||
},
|
||||
], (err, uploads) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(true, Array.isArray(uploads));
|
||||
assert.strictEqual(0, uploads.length);
|
||||
done();
|
||||
});
|
||||
const uploads = await posts.uploads.list(reply.pid);
|
||||
assert.strictEqual(true, Array.isArray(uploads));
|
||||
assert.strictEqual(0, uploads.length);
|
||||
});
|
||||
});
|
||||
|
||||
158
test/search.js
158
test/search.js
@@ -2,6 +2,8 @@
|
||||
|
||||
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
|
||||
const nconf = require('nconf');
|
||||
|
||||
const db = require('./mocks/databasemock');
|
||||
@@ -25,45 +27,82 @@ describe('Search', () => {
|
||||
let cid2;
|
||||
let cid3;
|
||||
|
||||
before(async () => {
|
||||
phoebeUid = await user.create({ username: 'phoebe' });
|
||||
gingerUid = await user.create({ username: 'ginger' });
|
||||
cid1 = (await categories.create({
|
||||
name: 'Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
})).cid;
|
||||
before((done) => {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.series({
|
||||
phoebe: function (next) {
|
||||
user.create({ username: 'phoebe' }, next);
|
||||
},
|
||||
ginger: function (next) {
|
||||
user.create({ username: 'ginger' }, next);
|
||||
},
|
||||
category1: function (next) {
|
||||
categories.create({
|
||||
name: 'Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
}, next);
|
||||
},
|
||||
category2: function (next) {
|
||||
categories.create({
|
||||
name: 'Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
}, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
phoebeUid = results.phoebe;
|
||||
gingerUid = results.ginger;
|
||||
cid1 = results.category1.cid;
|
||||
cid2 = results.category2.cid;
|
||||
|
||||
cid2 = (await categories.create({
|
||||
name: 'Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
})).cid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
categories.create({
|
||||
name: 'Child Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
parentCid: cid2,
|
||||
}, next);
|
||||
},
|
||||
function (category, next) {
|
||||
cid3 = category.cid;
|
||||
topics.post({
|
||||
uid: phoebeUid,
|
||||
cid: cid1,
|
||||
title: 'nodebb mongodb bugs',
|
||||
content: 'avocado cucumber apple orange fox',
|
||||
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'jquery'],
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
topic1Data = results.topicData;
|
||||
post1Data = results.postData;
|
||||
|
||||
cid3 = (await categories.create({
|
||||
name: 'Child Test Category',
|
||||
description: 'Test category created by testing script',
|
||||
parentCid: cid2,
|
||||
})).cid;
|
||||
|
||||
({ topicData: topic1Data, postData: post1Data } = await topics.post({
|
||||
uid: phoebeUid,
|
||||
cid: cid1,
|
||||
title: 'nodebb mongodb bugs',
|
||||
content: 'avocado cucumber apple orange fox',
|
||||
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'jquery'],
|
||||
}));
|
||||
|
||||
({ topicData: topic2Data, postData: post2Data } = await topics.post({
|
||||
uid: gingerUid,
|
||||
cid: cid2,
|
||||
title: 'java mongodb redis',
|
||||
content: 'avocado cucumber carrot armadillo',
|
||||
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'javascript'],
|
||||
}));
|
||||
post3Data = await topics.reply({
|
||||
uid: phoebeUid,
|
||||
content: 'reply post apple',
|
||||
tid: topic2Data.tid,
|
||||
});
|
||||
topics.post({
|
||||
uid: gingerUid,
|
||||
cid: cid2,
|
||||
title: 'java mongodb redis',
|
||||
content: 'avocado cucumber carrot armadillo',
|
||||
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'javascript'],
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
topic2Data = results.topicData;
|
||||
post2Data = results.postData;
|
||||
topics.reply({
|
||||
uid: phoebeUid,
|
||||
content: 'reply post apple',
|
||||
tid: topic2Data.tid,
|
||||
}, next);
|
||||
},
|
||||
function (_post3Data, next) {
|
||||
post3Data = _post3Data;
|
||||
setTimeout(next, 500);
|
||||
},
|
||||
], next);
|
||||
},
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should search term in titles and posts', async () => {
|
||||
@@ -181,24 +220,33 @@ describe('Search', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should search child categories', async () => {
|
||||
await topics.post({
|
||||
uid: gingerUid,
|
||||
cid: cid3,
|
||||
title: 'child category topic',
|
||||
content: 'avocado cucumber carrot armadillo',
|
||||
});
|
||||
const result = await search.search({
|
||||
query: 'avocado',
|
||||
searchIn: 'titlesposts',
|
||||
categories: [cid2],
|
||||
searchChildren: true,
|
||||
sortBy: 'topic.timestamp',
|
||||
sortDirection: 'desc',
|
||||
});
|
||||
assert(result.posts.length, 2);
|
||||
assert(result.posts[0].topic.title === 'child category topic');
|
||||
assert(result.posts[1].topic.title === 'java mongodb redis');
|
||||
it('should search child categories', (done) => {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
topics.post({
|
||||
uid: gingerUid,
|
||||
cid: cid3,
|
||||
title: 'child category topic',
|
||||
content: 'avocado cucumber carrot armadillo',
|
||||
}, next);
|
||||
},
|
||||
function (result, next) {
|
||||
search.search({
|
||||
query: 'avocado',
|
||||
searchIn: 'titlesposts',
|
||||
categories: [cid2],
|
||||
searchChildren: true,
|
||||
sortBy: 'topic.timestamp',
|
||||
sortDirection: 'desc',
|
||||
}, next);
|
||||
},
|
||||
function (result, next) {
|
||||
assert(result.posts.length, 2);
|
||||
assert(result.posts[0].topic.title === 'child category topic');
|
||||
assert(result.posts[1].topic.title === 'java mongodb redis');
|
||||
next();
|
||||
},
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should return json search data with no categories', async () => {
|
||||
|
||||
@@ -9,8 +9,10 @@ const util = require('util');
|
||||
|
||||
const sleep = util.promisify(setTimeout);
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
const nconf = require('nconf');
|
||||
|
||||
|
||||
const db = require('./mocks/databasemock');
|
||||
const user = require('../src/user');
|
||||
const groups = require('../src/groups');
|
||||
@@ -431,38 +433,20 @@ describe('socket.io', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('install/upgrade plugin', () => {
|
||||
it('should toggle plugin install', function (done) {
|
||||
this.timeout(0);
|
||||
const oldValue = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'development';
|
||||
socketAdmin.plugins.toggleInstall({
|
||||
uid: adminUid,
|
||||
}, {
|
||||
id: 'nodebb-plugin-location-to-map',
|
||||
version: 'latest',
|
||||
}, (err, data) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.name, 'nodebb-plugin-location-to-map');
|
||||
process.env.NODE_ENV = oldValue;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should upgrade plugin', function (done) {
|
||||
this.timeout(0);
|
||||
const oldValue = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'development';
|
||||
socketAdmin.plugins.upgrade({
|
||||
uid: adminUid,
|
||||
}, {
|
||||
id: 'nodebb-plugin-location-to-map',
|
||||
version: 'latest',
|
||||
}, (err) => {
|
||||
assert.ifError(err);
|
||||
process.env.NODE_ENV = oldValue;
|
||||
done();
|
||||
});
|
||||
it('should toggle plugin install', function (done) {
|
||||
this.timeout(0);
|
||||
const oldValue = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'development';
|
||||
socketAdmin.plugins.toggleInstall({
|
||||
uid: adminUid,
|
||||
}, {
|
||||
id: 'nodebb-plugin-location-to-map',
|
||||
version: 'latest',
|
||||
}, (err, data) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.name, 'nodebb-plugin-location-to-map');
|
||||
process.env.NODE_ENV = oldValue;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -491,6 +475,22 @@ describe('socket.io', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should upgrade plugin', function (done) {
|
||||
this.timeout(0);
|
||||
const oldValue = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'development';
|
||||
socketAdmin.plugins.upgrade({
|
||||
uid: adminUid,
|
||||
}, {
|
||||
id: 'nodebb-plugin-location-to-map',
|
||||
version: 'latest',
|
||||
}, (err) => {
|
||||
assert.ifError(err);
|
||||
process.env.NODE_ENV = oldValue;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should error with invalid data', (done) => {
|
||||
socketAdmin.widgets.set({ uid: adminUid }, null, (err) => {
|
||||
assert.equal(err.message, '[[error:invalid-data]]');
|
||||
@@ -683,43 +683,60 @@ describe('socket.io', () => {
|
||||
assert(pwExpiry > then && pwExpiry < Date.now());
|
||||
});
|
||||
|
||||
it('should not error on valid email', async () => {
|
||||
await socketUser.reset.send({ uid: 0 }, 'regular@test.com');
|
||||
const [count, eventsData] = await Promise.all([
|
||||
db.sortedSetCount('reset:issueDate', 0, Date.now()),
|
||||
events.getEvents('', 0, 0),
|
||||
]);
|
||||
assert.strictEqual(count, 2);
|
||||
it('should not error on valid email', (done) => {
|
||||
socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => {
|
||||
assert.ifError(err);
|
||||
|
||||
// Event validity
|
||||
assert.strictEqual(eventsData.length, 1);
|
||||
const event = eventsData[0];
|
||||
assert.strictEqual(event.type, 'password-reset');
|
||||
assert.strictEqual(event.text, '[[success:success]]');
|
||||
async.parallel({
|
||||
count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()),
|
||||
event: async.apply(events.getEvents, '', 0, 0),
|
||||
}, (err, data) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(data.count, 2);
|
||||
|
||||
// Event validity
|
||||
assert.strictEqual(data.event.length, 1);
|
||||
const event = data.event[0];
|
||||
assert.strictEqual(event.type, 'password-reset');
|
||||
assert.strictEqual(event.text, '[[success:success]]');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not generate code if rate limited', async () => {
|
||||
await assert.rejects(
|
||||
socketUser.reset.send({ uid: 0 }, 'regular@test.com'),
|
||||
{ message: '[[error:reset-rate-limited]]' },
|
||||
);
|
||||
const [count, eventsData] = await Promise.all([
|
||||
db.sortedSetCount('reset:issueDate', 0, Date.now()),
|
||||
events.getEvents('', 0, 0),
|
||||
]);
|
||||
assert.strictEqual(count, 2);
|
||||
it('should not generate code if rate limited', (done) => {
|
||||
socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => {
|
||||
assert(err);
|
||||
|
||||
// Event validity
|
||||
assert.strictEqual(eventsData.length, 1);
|
||||
const event = eventsData[0];
|
||||
assert.strictEqual(event.type, 'password-reset');
|
||||
assert.strictEqual(event.text, '[[error:reset-rate-limited]]');
|
||||
async.parallel({
|
||||
count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()),
|
||||
event: async.apply(events.getEvents, '', 0, 0),
|
||||
}, (err, data) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(data.count, 2);
|
||||
|
||||
// Event validity
|
||||
assert.strictEqual(data.event.length, 1);
|
||||
const event = data.event[0];
|
||||
assert.strictEqual(event.type, 'password-reset');
|
||||
assert.strictEqual(event.text, '[[error:reset-rate-limited]]');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error on invalid email (but not generate reset code)', async () => {
|
||||
await socketUser.reset.send({ uid: 0 }, 'irregular@test.com');
|
||||
const count = await db.sortedSetCount('reset:issueDate', 0, Date.now());
|
||||
assert.strictEqual(count, 2);
|
||||
it('should not error on invalid email (but not generate reset code)', (done) => {
|
||||
socketUser.reset.send({ uid: 0 }, 'irregular@test.com', (err) => {
|
||||
assert.ifError(err);
|
||||
|
||||
db.sortedSetCount('reset:issueDate', 0, Date.now(), (err, count) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(count, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on no email', (done) => {
|
||||
|
||||
831
test/topics.js
831
test/topics.js
File diff suppressed because it is too large
Load Diff
@@ -425,13 +425,14 @@ describe('Upload Controllers', () => {
|
||||
|
||||
it('should fail to delete a file as a non-admin', async () => {
|
||||
const { response, body } = await request.delete(`${nconf.get('url')}/api/v3/files`, {
|
||||
body: {
|
||||
data: {
|
||||
path: '/system/test.png',
|
||||
},
|
||||
jar: regularJar,
|
||||
headers: {
|
||||
'x-csrf-token': regular_csrf_token,
|
||||
},
|
||||
validateStatus: null,
|
||||
});
|
||||
assert.strictEqual(response.statusCode, 403);
|
||||
assert.deepStrictEqual(body.status, {
|
||||
|
||||
321
test/user.js
321
test/user.js
@@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const nconf = require('nconf');
|
||||
const validator = require('validator');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { setTimeout } = require('node:timers/promises');
|
||||
|
||||
const db = require('./mocks/databasemock');
|
||||
const User = require('../src/user');
|
||||
@@ -173,7 +173,7 @@ describe('User', () => {
|
||||
});
|
||||
|
||||
describe('.uniqueUsername()', () => {
|
||||
it('should deal with collisions', async () => {
|
||||
it('should deal with collisions', (done) => {
|
||||
const users = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
users.push({
|
||||
@@ -181,16 +181,25 @@ describe('User', () => {
|
||||
email: `jane.doe${i}@example.com`,
|
||||
});
|
||||
}
|
||||
for (const user of users) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await User.create(user);
|
||||
}
|
||||
|
||||
const username = await User.uniqueUsername({
|
||||
username: 'Jane Doe',
|
||||
userslug: 'jane-doe',
|
||||
});
|
||||
assert.strictEqual(username, 'Jane Doe 9');
|
||||
async.series([
|
||||
function (next) {
|
||||
async.eachSeries(users, (user, next) => {
|
||||
User.create(user, next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
User.uniqueUsername({
|
||||
username: 'Jane Doe',
|
||||
userslug: 'jane-doe',
|
||||
}, (err, username) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.strictEqual(username, 'Jane Doe 9');
|
||||
next();
|
||||
});
|
||||
},
|
||||
], done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -242,10 +251,12 @@ describe('User', () => {
|
||||
});
|
||||
|
||||
describe('.getModeratorUids()', () => {
|
||||
before(async () => {
|
||||
await groups.create({ name: 'testGroup' });
|
||||
await groups.join('cid:1:privileges:groups:moderate', 'testGroup');
|
||||
await groups.join('testGroup', 1);
|
||||
before((done) => {
|
||||
async.series([
|
||||
async.apply(groups.create, { name: 'testGroup' }),
|
||||
async.apply(groups.join, 'cid:1:privileges:groups:moderate', 'testGroup'),
|
||||
async.apply(groups.join, 'testGroup', 1),
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should retrieve all users with moderator bit in category privilege', (done) => {
|
||||
@@ -257,13 +268,38 @@ describe('User', () => {
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
groups.leave('cid:1:privileges:groups:moderate', 'testGroup');
|
||||
groups.destroy('testGroup');
|
||||
after((done) => {
|
||||
async.series([
|
||||
async.apply(groups.leave, 'cid:1:privileges:groups:moderate', 'testGroup'),
|
||||
async.apply(groups.destroy, 'testGroup'),
|
||||
], done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.isReadyToPost()', () => {
|
||||
it('should error when a user makes two posts in quick succession', (done) => {
|
||||
meta.config = meta.config || {};
|
||||
meta.config.postDelay = '10';
|
||||
|
||||
async.series([
|
||||
async.apply(Topics.post, {
|
||||
uid: testUid,
|
||||
title: 'Topic 1',
|
||||
content: 'lorem ipsum',
|
||||
cid: testCid,
|
||||
}),
|
||||
async.apply(Topics.post, {
|
||||
uid: testUid,
|
||||
title: 'Topic 2',
|
||||
content: 'lorem ipsum',
|
||||
cid: testCid,
|
||||
}),
|
||||
], (err) => {
|
||||
assert(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow a post if the last post time is > 10 seconds', (done) => {
|
||||
User.setUserField(testUid, 'lastposttime', +new Date() - (11 * 1000), () => {
|
||||
Topics.post({
|
||||
@@ -318,7 +354,7 @@ describe('User', () => {
|
||||
const titles = new Array(10).fill('topic title');
|
||||
const res = await Promise.allSettled(titles.map(async (title) => {
|
||||
const { body } = await helpers.request('post', '/api/v3/topics', {
|
||||
body: {
|
||||
data: {
|
||||
cid: testCid,
|
||||
title: title,
|
||||
content: 'the content',
|
||||
@@ -448,19 +484,32 @@ describe('User', () => {
|
||||
assert.equal(data.users[0].username, 'ipsearch_filter');
|
||||
});
|
||||
|
||||
it('should sort results by username', async () => {
|
||||
await User.create({ username: 'brian' });
|
||||
await User.create({ username: 'baris' });
|
||||
await User.create({ username: 'bzari' });
|
||||
const data = await User.search({
|
||||
uid: testUid,
|
||||
query: 'b',
|
||||
sortBy: 'username',
|
||||
paginate: false,
|
||||
it('should sort results by username', (done) => {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.create({ username: 'brian' }, next);
|
||||
},
|
||||
function (uid, next) {
|
||||
User.create({ username: 'baris' }, next);
|
||||
},
|
||||
function (uid, next) {
|
||||
User.create({ username: 'bzari' }, next);
|
||||
},
|
||||
function (uid, next) {
|
||||
User.search({
|
||||
uid: testUid,
|
||||
query: 'b',
|
||||
sortBy: 'username',
|
||||
paginate: false,
|
||||
}, next);
|
||||
},
|
||||
], (err, data) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.users[0].username, 'baris');
|
||||
assert.equal(data.users[1].username, 'brian');
|
||||
assert.equal(data.users[2].username, 'bzari');
|
||||
done();
|
||||
});
|
||||
assert.equal(data.users[0].username, 'baris');
|
||||
assert.equal(data.users[1].username, 'brian');
|
||||
assert.equal(data.users[2].username, 'bzari');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -945,7 +994,7 @@ describe('User', () => {
|
||||
headers: {
|
||||
'x-csrf-token': token,
|
||||
},
|
||||
body: {
|
||||
data: {
|
||||
type: 'external',
|
||||
url: 'https://example.org/picture.jpg',
|
||||
},
|
||||
@@ -1168,6 +1217,7 @@ describe('User', () => {
|
||||
// Accessing this page will mark the user's account as needing an updated email, below code undo's.
|
||||
await request.post(`${nconf.get('url')}/register/abort`, {
|
||||
jar,
|
||||
validateStatus: null,
|
||||
headers: {
|
||||
'x-csrf-token': csrf_token,
|
||||
},
|
||||
@@ -1214,12 +1264,30 @@ describe('User', () => {
|
||||
assert.equal(data[0].timestamp, now);
|
||||
});
|
||||
|
||||
it('should return the correct ban reason', async () => {
|
||||
await User.bans.ban(testUserUid, 0, '');
|
||||
const data = await User.getModerationHistory(testUserUid);
|
||||
assert.equal(data.bans.length, 1, 'one ban');
|
||||
assert.equal(data.bans[0].reason, '[[user:info.banned-no-reason]]', 'no ban reason');
|
||||
await User.bans.unban(testUserUid);
|
||||
it('should return the correct ban reason', (done) => {
|
||||
async.series([
|
||||
function (next) {
|
||||
User.bans.ban(testUserUid, 0, '', (err) => {
|
||||
assert.ifError(err);
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
User.getModerationHistory(testUserUid, (err, data) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.bans.length, 1, 'one ban');
|
||||
assert.equal(data.bans[0].reason, '[[user:info.banned-no-reason]]', 'no ban reason');
|
||||
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
], (err) => {
|
||||
assert.ifError(err);
|
||||
User.bans.unban(testUserUid, (err) => {
|
||||
assert.ifError(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ban user permanently', (done) => {
|
||||
@@ -1233,14 +1301,22 @@ describe('User', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should ban user temporarily', async () => {
|
||||
await User.bans.ban(testUserUid, Date.now() + 2000);
|
||||
let isBanned = await User.bans.isBanned(testUserUid);
|
||||
assert.equal(isBanned, true);
|
||||
await setTimeout(3000);
|
||||
isBanned = await User.bans.isBanned(testUserUid);
|
||||
assert.equal(isBanned, false);
|
||||
await User.bans.unban(testUserUid);
|
||||
it('should ban user temporarily', (done) => {
|
||||
User.bans.ban(testUserUid, Date.now() + 2000, (err) => {
|
||||
assert.ifError(err);
|
||||
|
||||
User.bans.isBanned(testUserUid, (err, isBanned) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(isBanned, true);
|
||||
setTimeout(() => {
|
||||
User.bans.isBanned(testUserUid, (err, isBanned) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(isBanned, false);
|
||||
User.bans.unban(testUserUid, done);
|
||||
});
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should error if until is NaN', (done) => {
|
||||
@@ -1318,19 +1394,26 @@ describe('User', () => {
|
||||
describe('Digest.getSubscribers', () => {
|
||||
const uidIndex = {};
|
||||
|
||||
before(async () => {
|
||||
before((done) => {
|
||||
const testUsers = ['daysub', 'offsub', 'nullsub', 'weeksub'];
|
||||
await Promise.all(testUsers.map(async (username) => {
|
||||
const uid = await User.create({ username, email: `${username}@example.com` });
|
||||
if (username === 'nullsub') {
|
||||
return;
|
||||
}
|
||||
uidIndex[username] = uid;
|
||||
async.each(testUsers, (username, next) => {
|
||||
async.waterfall([
|
||||
async.apply(User.create, { username: username, email: `${username}@example.com` }),
|
||||
function (uid, next) {
|
||||
if (username === 'nullsub') {
|
||||
return setImmediate(next);
|
||||
}
|
||||
|
||||
const sub = username.slice(0, -3);
|
||||
await User.updateDigestSetting(uid, sub);
|
||||
await User.setSetting(uid, 'dailyDigestFreq', sub);
|
||||
}));
|
||||
uidIndex[username] = uid;
|
||||
|
||||
const sub = username.slice(0, -3);
|
||||
async.parallel([
|
||||
async.apply(User.updateDigestSetting, uid, sub),
|
||||
async.apply(User.setSetting, uid, 'dailyDigestFreq', sub),
|
||||
], next);
|
||||
},
|
||||
], next);
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should accurately build digest list given ACP default "null" (not set)', (done) => {
|
||||
@@ -1342,38 +1425,71 @@ describe('User', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should accurately build digest list given ACP default "day"', async () => {
|
||||
await meta.configs.set('dailyDigestFreq', 'day');
|
||||
const subs = await User.digest.getSubscribers('day');
|
||||
it('should accurately build digest list given ACP default "day"', (done) => {
|
||||
async.series([
|
||||
async.apply(meta.configs.set, 'dailyDigestFreq', 'day'),
|
||||
function (next) {
|
||||
User.digest.getSubscribers('day', (err, subs) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), true); // daysub does get emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), false); // weeksub does not get emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub doesn't get emailed
|
||||
|
||||
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), true); // daysub does get emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), false); // weeksub does not get emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub doesn't get emailed
|
||||
next();
|
||||
});
|
||||
},
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should accurately build digest list given ACP default "week"', async () => {
|
||||
await meta.configs.set('dailyDigestFreq', 'week');
|
||||
const subs = await User.digest.getSubscribers('week');
|
||||
it('should accurately build digest list given ACP default "week"', (done) => {
|
||||
async.series([
|
||||
async.apply(meta.configs.set, 'dailyDigestFreq', 'week'),
|
||||
function (next) {
|
||||
User.digest.getSubscribers('week', (err, subs) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), true); // weeksub gets emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), false); // daysub gets emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub does not get emailed
|
||||
|
||||
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), true); // weeksub gets emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), false); // daysub gets emailed
|
||||
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub does not get emailed
|
||||
next();
|
||||
});
|
||||
},
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should accurately build digest list given ACP default "off"', async () => {
|
||||
await meta.configs.set('dailyDigestFreq', 'off');
|
||||
const subs = await User.digest.getSubscribers('day');
|
||||
assert.strictEqual(subs.length, 1);
|
||||
it('should accurately build digest list given ACP default "off"', (done) => {
|
||||
async.series([
|
||||
async.apply(meta.configs.set, 'dailyDigestFreq', 'off'),
|
||||
function (next) {
|
||||
User.digest.getSubscribers('day', (err, subs) => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(subs.length, 1);
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
], done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('digests', () => {
|
||||
let uid;
|
||||
before(async () => {
|
||||
uid = await User.create({ username: 'digestuser', email: 'test@example.com' });
|
||||
await User.updateDigestSetting(uid, 'day');
|
||||
await User.setSetting(uid, 'dailyDigestFreq', 'day');
|
||||
await User.setSetting(uid, 'notificationType_test', 'notificationemail');
|
||||
before((done) => {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.create({ username: 'digestuser', email: 'test@example.com' }, next);
|
||||
},
|
||||
function (_uid, next) {
|
||||
uid = _uid;
|
||||
User.updateDigestSetting(uid, 'day', next);
|
||||
},
|
||||
function (next) {
|
||||
User.setSetting(uid, 'dailyDigestFreq', 'day', next);
|
||||
},
|
||||
function (next) {
|
||||
User.setSetting(uid, 'notificationType_test', 'notificationemail', next);
|
||||
},
|
||||
], done);
|
||||
});
|
||||
|
||||
it('should send digests', async () => {
|
||||
@@ -1449,7 +1565,7 @@ describe('User', () => {
|
||||
uid: uid,
|
||||
}, nconf.get('secret'));
|
||||
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`, { validateStatus: null });
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -1459,12 +1575,12 @@ describe('User', () => {
|
||||
uid: uid,
|
||||
}, nconf.get('secret'));
|
||||
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`, { validateStatus: null });
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it('should return errors on missing token', async () => {
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/`);
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/`, { validateStatus: null });
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
|
||||
@@ -1475,7 +1591,7 @@ describe('User', () => {
|
||||
uid: uid,
|
||||
}, `${nconf.get('secret')}aababacaba`);
|
||||
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
|
||||
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`, { validateStatus: null });
|
||||
assert.strictEqual(response.statusCode, 403);
|
||||
});
|
||||
});
|
||||
@@ -1676,17 +1792,36 @@ describe('User', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should set moderation note', async () => {
|
||||
const adminUid = await User.create({ username: 'noteadmin' });
|
||||
await groups.join('administrators', adminUid);
|
||||
await socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'this is a test user' });
|
||||
await setTimeout(50);
|
||||
await socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: '<svg/onload=alert(document.location);//' });
|
||||
const notes = await User.getModerationNotes(testUid, 0, -1);
|
||||
assert.equal(notes[0].note, '<svg/onload=alert(document.location);//');
|
||||
assert.equal(notes[0].uid, adminUid);
|
||||
assert.equal(notes[1].note, 'this is a test user');
|
||||
assert(notes[0].timestamp);
|
||||
it('should set moderation note', (done) => {
|
||||
let adminUid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.create({ username: 'noteadmin' }, next);
|
||||
},
|
||||
function (_adminUid, next) {
|
||||
adminUid = _adminUid;
|
||||
groups.join('administrators', adminUid, next);
|
||||
},
|
||||
function (next) {
|
||||
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'this is a test user' }, next);
|
||||
},
|
||||
function (next) {
|
||||
setTimeout(next, 50);
|
||||
},
|
||||
function (next) {
|
||||
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: '<svg/onload=alert(document.location);//' }, next);
|
||||
},
|
||||
function (next) {
|
||||
User.getModerationNotes(testUid, 0, -1, next);
|
||||
},
|
||||
], (err, notes) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(notes[0].note, '<svg/onload=alert(document.location);//');
|
||||
assert.equal(notes[0].uid, adminUid);
|
||||
assert.equal(notes[1].note, 'this is a test user');
|
||||
assert(notes[0].timestamp);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get unread count 0 for guest', async () => {
|
||||
|
||||
Reference in New Issue
Block a user