Compare commits

...

17 Commits

Author SHA1 Message Date
snyk-bot 37a1465f84 fix: upgrade prismjs from 1.23.0 to 1.29.0
Snyk has created this PR to upgrade prismjs from 1.23.0 to 1.29.0.

See this package in npm:
https://www.npmjs.com/package/prismjs

See this project in Snyk:
https://app.snyk.io/org/klemek/project/81af650f-8c8e-4116-b289-95415c85d8a7?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-10-08 12:54:44 +00:00
Klemek ab23e4aa3c Create docker.yml 2021-06-09 15:34:57 +02:00
Klemek ab573f91ee Merge pull request #58 from Klemek/f-stats-all
/stats?all=true
2021-04-06 10:10:00 +02:00
Klemek 8a9b9cdcfe /stats?all=true 2021-04-06 09:58:51 +02:00
Klemek e56867a269 Update bot_detector.js 2021-04-04 23:33:59 +02:00
Klemek 8e795c6371 Merge pull request #57 from Klemek/f-ignore-bots
ignore bots in hit counter
2021-04-04 23:31:38 +02:00
Klemek c3e53c7df8 more unit tests 2021-04-04 23:24:33 +02:00
Klemek 404b02830d unit testing 2021-04-04 22:48:21 +02:00
Klemek 2fe9a8fecd updated package.json 2021-04-04 21:57:54 +02:00
Klemek 078f3d7416 updated readme 2021-04-04 21:57:16 +02:00
Klemek d69e10202c bot detector handling requests and disabling hit counter 2021-04-04 21:57:09 +02:00
Klemek 140e472e29 bot detector base code 2021-04-04 21:56:39 +02:00
Klemek 823d97f4bb parseInt redis values 2021-03-30 20:19:37 +02:00
Klemek f7167a85a8 1.3.1: current visitors 2021-03-30 20:16:15 +02:00
Klemek 4a3b8267ec update Dockerfile 2021-03-30 19:56:08 +02:00
Klemek d0bebcba87 fix Dockerfile 2021-03-30 19:53:26 +02:00
Klemek 56d7993116 bump to node 15 2021-03-30 19:47:18 +02:00
13 changed files with 476 additions and 229 deletions
+34
View File
@@ -0,0 +1,34 @@
name: Docker
on: ["push", "pull_request"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: false
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
+1
View File
@@ -2,6 +2,7 @@
/node_modules
/config.json
/config.example.json
/robots_list.json
/data
/data/*
/test_data
+5 -1
View File
@@ -1,12 +1,16 @@
FROM node:14
FROM node:15
# Create app directory
WORKDIR /usr/src/app
VOLUME [ "/usr/src/app/data" ]
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
COPY src/postinstall.js ./src/postinstall.js
COPY src/config.default.json ./src/config.default.json
RUN npm install
# If you are building your code for production
+5
View File
@@ -336,6 +336,11 @@ Any URL like `/year/month/day/anything/` will redirect to this article (and link
* `hit_counter`
* `unique_visitor_timeout`: (default: 7200000 / 2h)
specify the time (in ms) before a visitor can be accounted again
* `robots`
* `list_url`: (default: https://raw.githubusercontent.com/atmire/COUNTER-Robots/master/COUNTER_Robots_list.json)
url to fetch for web crawlers patterns
* `list_file`: (default: robots_list.json)
file to store web crawlers patterns
* `redis`
Options to connect to redis (see [redis options](https://github.com/NodeRedis/node-redis#options-object-properties) for more info)
* `host`: (default: localhost)
+12 -132
View File
@@ -1,11 +1,12 @@
{
"name": "gitblog.md",
"version": "1.2.8",
"version": "1.3.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.2.8",
"name": "gitblog.md",
"version": "1.3.3",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
@@ -18,7 +19,7 @@
"mathjax-node": "^2.1.1",
"ncp": "^2.0.0",
"node-prismjs": "^0.1.0",
"prismjs": "^1.23.0",
"prismjs": "^1.29.0",
"redis": "^3.0.2",
"rss": "^1.2.2",
"showdown": "^1.9.1"
@@ -1502,17 +1503,6 @@
"node": ">=0.10.0"
}
},
"node_modules/clipboard": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
"integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
"optional": true,
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"node_modules/cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -1892,12 +1882,6 @@
"node": ">=0.4.0"
}
},
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"node_modules/denque": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
@@ -3997,15 +3981,6 @@
"node": ">=8"
}
},
"node_modules/good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"dependencies": {
"delegate": "^3.1.2"
}
},
"node_modules/graceful-fs": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
@@ -6233,14 +6208,6 @@
"prismjs": "^1.5.0"
}
},
"node_modules/node-prismjs/node_modules/prismjs": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz",
"integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=",
"optionalDependencies": {
"clipboard": "^1.5.5"
}
},
"node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -6727,22 +6694,11 @@
}
},
"node_modules/prismjs": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"optionalDependencies": {
"clipboard": "^2.0.0"
}
},
"node_modules/prismjs/node_modules/clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"optional": true,
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"engines": {
"node": ">=6"
}
},
"node_modules/process-nextick-args": {
@@ -7387,12 +7343,6 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -8315,12 +8265,6 @@
"integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=",
"dev": true
},
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"node_modules/tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
@@ -10091,17 +10035,6 @@
}
}
},
"clipboard": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
"integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -10410,12 +10343,6 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"denque": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
@@ -11997,15 +11924,6 @@
}
}
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"graceful-fs": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
@@ -13787,16 +13705,6 @@
"integrity": "sha1-lrLxpU24xgj7kjbR2BDpqLATKGA=",
"requires": {
"prismjs": "^1.5.0"
},
"dependencies": {
"prismjs": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz",
"integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=",
"requires": {
"clipboard": "^1.5.5"
}
}
}
},
"normalize-package-data": {
@@ -14171,25 +14079,9 @@
}
},
"prismjs": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"requires": {
"clipboard": "^2.0.0"
},
"dependencies": {
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
}
}
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="
},
"process-nextick-args": {
"version": "2.0.1",
@@ -14676,12 +14568,6 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -15444,12 +15330,6 @@
"integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=",
"dev": true
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"tmpl": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "gitblog.md",
"version": "1.3.0",
"version": "1.3.3",
"description": "A static blog using Markdown pulled from your git repository.",
"main": "src/server.js",
"dependencies": {
@@ -13,7 +13,7 @@
"mathjax-node": "^2.1.1",
"ncp": "^2.0.0",
"node-prismjs": "^0.1.0",
"prismjs": "^1.23.0",
"prismjs": "^1.29.0",
"redis": "^3.0.2",
"rss": "^1.2.2",
"showdown": "^1.9.1"
+40 -10
View File
@@ -61,6 +61,23 @@ module.exports = (config) => {
}
},
);
const botDetector = require('./bot_detector')(config);
botDetector.load((status, err) => {
switch (status) {
case botDetector.status.FETCH_OK:
console.log(cons.ok, 'fetched robots list');
break;
case botDetector.status.FETCH_ERROR:
console.error(cons.error, 'error fetching robots list : ' + err);
break;
case botDetector.status.READ_OK:
console.log(cons.ok, `read robots list: ${botDetector.count}`);
break;
case botDetector.status.READ_ERROR:
console.error(cons.error, 'error reading robots list : ' + err);
break;
}
});
// set view engine from configuration
app.set('view engine', config['view_engine']);
@@ -145,6 +162,9 @@ module.exports = (config) => {
});
app.use(limiter);
//detect robots
app.use(botDetector.handle);
//log request at result end
app.use((req, res, next) => {
if (config['access_log']) {
@@ -168,7 +188,7 @@ module.exports = (config) => {
if (err) {
showError(req, res, 404);
} else {
hc.count(req, '/', () => {
hc.count(req, '/', req.isRobot, () => {
render(req, res, homePath,
{
articles: Object.values(articles)
@@ -181,12 +201,25 @@ module.exports = (config) => {
});
app.get('/stats', (req, res) => {
if (config['modules']['hit_counter']) {
if (req.query['all']) {
const keys = Object.keys(articles).filter(key => !articles[key].draft);
keys.unshift('/');
const read = (i, outputData) => {
if (i >= keys.length) {
res.json(outputData);
} else {
hc.read(keys[i], (data) => {
outputData.push(data);
read(i + 1, outputData);
});
}
};
read(0, []);
} else {
hc.read('/', (data) => {
res.json({
hits: data.hits,
visitors: data.visitors,
});
res.json(data);
});
}
} else {
showError(req, res, 404);
}
@@ -268,16 +301,13 @@ module.exports = (config) => {
} else if (req.path.endsWith('stats')) {
if (config['modules']['hit_counter']) {
hc.read(articlePath, (data) => {
res.json({
hits: data.hits,
visitors: data.visitors,
});
res.json(data);
});
} else {
showError(req, res, 404);
}
} else {
hc.count(req, articlePath, () => {
hc.count(req, articlePath, req.isRobot, () => {
renderer.render(article.realPath, (err, html) => {
if (err) {
console.log(cons.error, `failed to render article ${req.path} : ${err}`);
+66
View File
@@ -0,0 +1,66 @@
const https = require('https');
const fs = require('fs');
module.exports = (config) => {
const _this = {
status: {
FETCH_OK: 1,
FETCH_ERROR: 2,
READ_OK: 3,
READ_ERROR: 4,
},
count: [],
regex: null,
};
const fetchList = (cb) => {
https.get(config['robots']['list_url'], (res) => {
if (res.statusCode !== 200) {
cb(res.statusCode);
} else {
const file = fs.createWriteStream(config['robots']['list_file']);
res.pipe(file);
file.on('finish', () => {
file.close(cb);
});
}
}).on('error', (err) => {
cb(err.message);
});
};
const readFile = (cb) => {
fs.readFile(config['robots']['list_file'], { encoding: 'utf-8' }, (err, data) => {
if (err) {
cb(err, undefined);
} else {
try {
cb(undefined, JSON.parse(data));
} catch (err) {
cb(err, undefined);
}
}
});
};
_this.load = (cb) => {
fetchList((err) => {
cb(err ? _this.status.FETCH_ERROR : _this.status.FETCH_OK, err);
readFile((err, data) => {
if (!err) {
_this.count = data.length;
_this.regex = new RegExp('(' + data.filter(v => v['pattern']).map(v => v['pattern'])
.join('|') + ')');
}
cb(err ? _this.status.READ_ERROR : _this.status.READ_OK, err);
});
});
};
_this.handle = (req, res, next) => {
req.isRobot = !!((req.headers['user-agent'] || '').match(_this.regex));
next();
};
return _this;
};
+4
View File
@@ -63,6 +63,10 @@
"hit_counter": {
"unique_visitor_timeout": 7200000
},
"robots": {
"list_url": "https://raw.githubusercontent.com/atmire/COUNTER-Robots/master/COUNTER_Robots_list.json",
"list_file": "robots_list.json"
},
"redis": {
"host": "localhost",
"port": 6379
+26 -8
View File
@@ -8,15 +8,15 @@ module.exports = (config, onConnect, onError) => {
const visitors = {};
const count = (req, path, cb) => {
if (!client.connected) {
const count = (req, path, disable, cb) => {
if (!client.connected || disable) {
cb();
} else {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
const key = path + ':' + ip;
visitors[path] = (visitors[path] || {});
const now = Date.now();
const isNewVisitor = (now - (visitors[key] || 0)) > config['hit_counter']['unique_visitor_timeout'];
visitors[key] = now;
const isNewVisitor = (now - (visitors[path][ip] || 0)) > config['hit_counter']['unique_visitor_timeout'];
visitors[path][ip] = now;
client
.multi()
.hincrby(path, 'h', 1)
@@ -25,17 +25,35 @@ module.exports = (config, onConnect, onError) => {
}
};
const cleanVisitors = (path) => {
visitors[path] = (visitors[path] || {});
const now = Date.now();
let count = 0;
for (let ip in visitors[path]) {
if ((now - visitors[path][ip]) > config['hit_counter']['unique_visitor_timeout']) {
delete visitors[path][ip];
} else {
count++;
}
}
return count;
};
const read = (path, cb) => {
if (!client.connected) {
cb({
path: path,
hits: 0,
visitors: 0,
total_visitors: 0,
current_visitors: cleanVisitors(path),
});
} else {
client.hgetall(path, (_, value) => {
cb({
hits: value ? value.h || 0 : 0,
visitors: value ? value.v || 0 : 0,
path: path,
hits: value ? parseInt(value.h) || 0 : 0,
total_visitors: value ? parseInt(value.v) || 0 : 0,
current_visitors: cleanVisitors(path),
});
});
}
+110 -49
View File
@@ -197,22 +197,6 @@ describe('Test root path', () => {
});
}, fail);
});
test('404 index no stats', (done) => {
request(app).get('/stats')
.then((response) => {
expect(response.statusCode).toBe(404);
done();
});
});
test('200 index stats', (done) => {
config['modules']['hit_counter'] = true;
request(app).get('/stats')
.then((response) => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({ hits: 0, visitors: 0 });
done();
});
});
});
describe('Test RSS feed', () => {
@@ -451,40 +435,7 @@ describe('Test articles rendering', () => {
});
}, fail);
});
test('404 article no stats', (done) => {
utils.createEmptyDirs([ path.join(dataDir, '2019', '05', '05') ]);
utils.createEmptyFiles([
path.join(dataDir, '2019', '05', '05', 'index.md'),
path.join(dataDir, testTemplate),
]);
app.reload(() => {
request(app).get('/2019/05/05/hello/stats')
.then((response) => {
expect(response.statusCode).toBe(404);
done();
});
});
});
test('200 index stats', (done) => {
config['modules']['hit_counter'] = true;
utils.createEmptyDirs([ path.join(dataDir, '2019', '05', '05') ]);
utils.createEmptyFiles([
path.join(dataDir, '2019', '05', '05', 'index.md'),
path.join(dataDir, testTemplate),
]);
app.reload(() => {
request(app).get('/2019/05/05/anything/stats')
.then((response) => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({ hits: 0, visitors: 0 });
done();
});
});
});
});
describe('Test static files', () => {
test('404 invalid file no error page', (done) => {
@@ -566,3 +517,113 @@ describe('Test other requests', () => {
});
});
});
describe('Test stats', () => {
test('404 index no stats', (done) => {
request(app).get('/stats')
.then((response) => {
expect(response.statusCode).toBe(404);
done();
});
});
test('200 index stats', (done) => {
config['modules']['hit_counter'] = true;
request(app).get('/stats')
.then((response) => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({
path: '/',
hits: 0,
total_visitors: 0,
current_visitors: 0,
});
done();
});
});
test('200 index stats all no article', (done) => {
config['modules']['hit_counter'] = true;
app.reload(() => {
request(app).get('/stats?all=true')
.then((response) => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual([
{
path: '/',
hits: 0,
total_visitors: 0,
current_visitors: 0,
},
]);
done();
});
});
});
test('200 index stats all 2 article 1 drafted', (done) => {
config['modules']['hit_counter'] = true;
utils.createEmptyDirs([
path.join(dataDir, '2019', '05', '05'),
path.join(dataDir, '2019', '04', '05'),
]);
utils.createEmptyFiles([
path.join(dataDir, '2019', '05', '05', 'index.md'),
path.join(dataDir, '2019', '04', '05', 'draft.md'),
]);
app.reload(() => {
request(app).get('/stats?all=true')
.then((response) => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual([
{
path: '/',
hits: 0,
total_visitors: 0,
current_visitors: 0,
},
{
path: '2019/05/05',
hits: 0,
total_visitors: 0,
current_visitors: 0,
},
]);
done();
});
});
});
test('404 article no stats', (done) => {
utils.createEmptyDirs([ path.join(dataDir, '2019', '05', '05') ]);
utils.createEmptyFiles([
path.join(dataDir, '2019', '05', '05', 'index.md'),
path.join(dataDir, testTemplate),
]);
app.reload(() => {
request(app).get('/2019/05/05/hello/stats')
.then((response) => {
expect(response.statusCode).toBe(404);
done();
});
});
});
test('200 article stats', (done) => {
config['modules']['hit_counter'] = true;
utils.createEmptyDirs([ path.join(dataDir, '2019', '05', '05') ]);
utils.createEmptyFiles([
path.join(dataDir, '2019', '05', '05', 'index.md'),
path.join(dataDir, testTemplate),
]);
app.reload(() => {
request(app).get('/2019/05/05/anything/stats')
.then((response) => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({
path: '2019/05/05',
hits: 0,
total_visitors: 0,
current_visitors: 0,
});
done();
});
});
});
});
+116
View File
@@ -0,0 +1,116 @@
const fs = require('fs');
const utils = require('./test_utils');
const dataDir = 'test_data';
const config = {
robots: {
list_url: '',
list_file: `${dataDir}/robots_list.json`,
},
};
beforeAll(() => {
utils.deleteFolderSync(dataDir);
fs.mkdirSync(dataDir);
});
afterAll(() => {
if (fs.existsSync(dataDir)) {
utils.deleteFolderSync(dataDir);
}
});
const botDetector = require('../src/bot_detector')(config);
describe('load()', () => {
test('success', (done) => {
config.robots = {
list_url: 'https://raw.githubusercontent.com/atmire/COUNTER-Robots/master/COUNTER_Robots_list.json',
list_file: `${dataDir}/robots_list_success.json`,
};
let count = 0;
botDetector.load((status, err) => {
expect(err).not.toBeDefined();
expect(status).toBe(count === 0 ? botDetector.status.FETCH_OK : botDetector.status.READ_OK);
if (count > 0) {
done();
}
count++;
});
});
test('fetch and file failure', (done) => {
let count = 0;
config.robots = {
list_url: 'https://blog.klemek.fr/invalid.json',
list_file: `${dataDir}/robots_list_fail_1.json`,
};
botDetector.load((status) => {
expect(status).toBe(count === 0 ? botDetector.status.FETCH_ERROR : botDetector.status.READ_ERROR);
if (count > 0) {
done();
}
count++;
});
});
test('fetch failure and file ok', (done) => {
let count = 0;
config.robots = {
list_url: 'https://blog.klemek.fr/invalid.json',
list_file: `${dataDir}/robots_list_fail_2.json`,
};
fs.writeFile(config.robots.list_file, '[]\n', { encoding: 'utf-8' }, () => {
botDetector.load((status) => {
expect(status).toBe(count === 0 ? botDetector.status.FETCH_ERROR : botDetector.status.READ_OK);
if (count > 0) {
done();
}
count++;
});
});
});
});
describe('handle()', () => {
beforeAll((done) => {
config.robots = {
list_url: 'https://blog.klemek.fr/invalid.json',
list_file: `${dataDir}/robots_list_fake.json`,
};
fs.writeFile(config.robots.list_file, '[{"pattern":"bot"}]\n', { encoding: 'utf-8' }, () => {
botDetector.load((status) => {
if (status !== botDetector.status.FETCH_ERROR) {
done();
}
});
});
});
test('not bot', (done) => {
const req = {
headers: {
'user-agent': 'my user agent',
},
};
botDetector.handle(req, null, () => {
expect(req.isRobot).toBeFalsy();
done();
});
});
test('bot', (done) => {
const req = {
headers: {
'user-agent': 'bot',
},
};
botDetector.handle(req, null, () => {
expect(req.isRobot).toBeTruthy();
done();
});
});
});
+78 -50
View File
@@ -37,55 +37,11 @@ test('options passed to redis', () => {
expect(mockClient.options).toEqual(config['redis']);
});
describe('read()', () => {
beforeEach(() => {
mockClient.hgetall = (_, cb) => {
cb();
};
});
test('read path', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb(undefined, { h: 12, v: 34 });
};
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(12);
expect(data.visitors).toBe(34);
done();
});
});
test('read path with error', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb('error', undefined);
};
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(0);
expect(data.visitors).toBe(0);
done();
});
});
test('read path with error 2', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb(undefined, {});
};
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(0);
expect(data.visitors).toBe(0);
done();
});
});
});
describe('count()', () => {
beforeEach(() => {
mockClient.multi = () => mockClient;
mockClient.hincrby = () => mockClient;
mockClient.exec = (cb) => {
@@ -94,6 +50,78 @@ describe('count()', () => {
config['hit_counter']['unique_visitor_timeout'] = -1;
});
describe('read()', () => {
test('normal', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb(undefined, { h: 12, v: 34 });
};
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(12);
expect(data.total_visitors).toBe(34);
expect(data.current_visitors).toBe(0);
done();
});
});
test('with error', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb('error', undefined);
};
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(0);
expect(data.total_visitors).toBe(0);
expect(data.current_visitors).toBe(0);
done();
});
});
test('with error 2', (done) => {
mockClient.hgetall = (path, cb) => {
expect(path).toBe('/test/path/');
cb(undefined, {});
};
hc.read('/test/path/', (data) => {
expect(data).toBeDefined();
expect(data.hits).toBe(0);
expect(data.total_visitors).toBe(0);
expect(data.current_visitors).toBe(0);
done();
});
});
test('1 visitor', (done) => {
config['hit_counter']['unique_visitor_timeout'] = 1000;
hc.count({
headers: {},
connection: { remoteAddress: 'test1' },
}, '/test/path/5', false, () => {
hc.read('/test/path/5', (data) => {
expect(data).toBeDefined();
expect(data.current_visitors).toBe(1);
done();
});
});
});
test('cleaned old visitor', (done) => {
hc.count({
headers: {},
connection: { remoteAddress: 'test1' },
}, '/test/path/5', false, () => {
hc.read('/test/path/5', (data) => {
expect(data).toBeDefined();
expect(data.current_visitors).toBe(0);
done();
});
});
});
});
describe('count()', () => {
test('simple visit', (done) => {
let multiCalled = false;
let execCalled = false;
@@ -117,7 +145,7 @@ describe('count()', () => {
hc.count({
headers: {},
connection: { remoteAddress: 'test1' },
}, '/test/path/1', () => {
}, '/test/path/1', false, () => {
expect(multiCalled).toBeTruthy();
expect(hincrbyCalls).toEqual([
[
@@ -149,11 +177,11 @@ describe('count()', () => {
hc.count({
headers: {},
connection: { remoteAddress: 'test2' },
}, '/test/path/2', () => {
}, '/test/path/2', false, () => {
hc.count({
headers: {},
connection: { remoteAddress: 'test2' },
}, '/test/path/2', () => {
}, '/test/path/2', false, () => {
expect(hincrbyCalls).toEqual([
[
'/test/path/2',
@@ -195,11 +223,11 @@ describe('count()', () => {
hc.count({
headers: {},
connection: { remoteAddress: 'test3' },
}, '/test/path/3', () => {
}, '/test/path/3', false, () => {
hc.count({
headers: {},
connection: { remoteAddress: 'test3' },
}, '/test/path/3', () => {
}, '/test/path/3', false, () => {
expect(hincrbyCalls).toEqual([
[
'/test/path/3',