blob: 9fc736c861d4db2cc23c632506bdef5bdd4d7fb7 [file] [log] [blame]
Marc Kupietz93d7f702025-06-27 15:41:48 +02001const https = require('https');
Marc Kupietzd0ee97e2025-12-04 15:46:14 +01002const crypto = require('crypto');
Marc Kupietz490b0532024-09-05 09:36:21 +02003const puppeteer = require('puppeteer-extra');
4puppeteer.use(require('puppeteer-extra-plugin-user-preferences')({
5 userPrefs: {
6 safebrowsing: {
7 enabled: false,
8 enhanced: false
9 }
10 }
11}));
Marc Kupietz55fc3162022-12-04 16:25:49 +010012const chai = require('chai');
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010013const { afterEach } = require('mocha');
Marc Kupietz55fc3162022-12-04 16:25:49 +010014const assert = chai.assert;
15const should = chai.should();
Marc Kupietz7f1666a2024-07-12 18:35:31 +020016var slack = null;
Marc Kupietz55fc3162022-12-04 16:25:49 +010017
Marc Kupietz0f6c54d2022-12-03 15:32:40 +010018const KORAP_URL = process.env.KORAP_URL || "http://localhost:64543";
Marc Kupietzbfb23012025-06-03 15:47:10 +020019const KORAP_LOGIN = 'KORAP_USERNAME' in process.env ? process.env.KORAP_USERNAME : 'KORAP_LOGIN' in process.env ? process.env.KORAP_LOGIN : "user2"
20const KORAP_PWD = process.env.KORAP_PWD || process.env.KORAP_PASSWORD || "password2";
Marc Kupietz26982382022-12-04 19:02:57 +010021const KORAP_QUERIES = process.env.KORAP_QUERIES || 'geht, [orth=geht & cmc/pos=VVFIN]'
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +020022const KORAP_MIN_TOKENS_IN_CORPUS = parseInt(process.env.KORAP_MIN_TOKENS_IN_CORPUS || "100000", 10);
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010023const korap_rc = require('../lib/korap_rc.js').new(KORAP_URL)
24
Marc Kupietz7f1666a2024-07-12 18:35:31 +020025const slack_webhook = process.env.SLACK_WEBHOOK_URL;
Marc Kupietzd0ee97e2025-12-04 15:46:14 +010026const nc_talk_url = process.env.NC_TALK_URL || 'https://cloud.ids-mannheim.de';
27const nc_talk_conversation = process.env.NC_TALK_CONVERSATION;
28const nc_talk_secret = process.env.NC_TALK_SECRET;
Marc Kupietz4d335a32024-09-04 16:13:48 +020029
Marc Kupietz7f1666a2024-07-12 18:35:31 +020030if (slack_webhook) {
31 slack = require('slack-notify')(slack_webhook);
32}
33
Marc Kupietzd0ee97e2025-12-04 15:46:14 +010034// Function to send message to Nextcloud Talk
35async function sendToNextcloudTalk(message, silent = false) {
36 if (!nc_talk_conversation || !nc_talk_secret) {
37 return;
38 }
39
40 try {
41 const axios = require('axios');
42
43 // Generate random header and signature
44 const randomHeader = crypto.randomBytes(32).toString('hex');
45 const messageToSign = randomHeader + message;
46 const signature = crypto.createHmac('sha256', nc_talk_secret)
47 .update(messageToSign)
48 .digest('hex');
49
50 // Send the message
51 await axios.post(
52 `${nc_talk_url}/ocs/v2.php/apps/spreed/api/v1/bot/${nc_talk_conversation}/message`,
53 {
54 message: message,
55 silent: silent
56 },
57 {
58 headers: {
59 'Content-Type': 'application/json',
60 'Accept': 'application/json',
61 'OCS-APIRequest': 'true',
62 'X-Nextcloud-Talk-Bot-Random': randomHeader,
63 'X-Nextcloud-Talk-Bot-Signature': signature
64 }
65 }
66 );
67 console.log('Message sent to Nextcloud Talk successfully');
68 } catch (error) {
69 console.error('Failed to send message to Nextcloud Talk:', error.message);
70 }
71}
72
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010073function ifConditionIt(title, condition, test) {
Marc Kupietz55fc3162022-12-04 16:25:49 +010074 return condition ? it(title, test) : it.skip(title + " (skipped)", test)
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010075}
Marc Kupietzc4077822022-12-03 15:32:40 +010076
Marc Kupietz0f6c54d2022-12-03 15:32:40 +010077describe('Running KorAP UI end-to-end tests on ' + KORAP_URL, () => {
Marc Kupietzc4077822022-12-03 15:32:40 +010078
Marc Kupietz93d7f702025-06-27 15:41:48 +020079 let browser;
80 let page;
81
82
Marc Kupietzc4077822022-12-03 15:32:40 +010083 before(async () => {
Marc Kupietz69e02802023-11-08 14:37:22 +010084 browser = await puppeteer.launch({
Marc Kupietzbfb23012025-06-03 15:47:10 +020085 headless: "shell",
86 args: [
87 '--no-sandbox',
88 '--disable-setuid-sandbox',
89 '--disable-dev-shm-usage',
90 '--disable-accelerated-2d-canvas',
91 '--no-first-run',
92 '--no-zygote',
93 '--disable-gpu'
94 ]
Marc Kupietz69e02802023-11-08 14:37:22 +010095 })
Marc Kupietzc4077822022-12-03 15:32:40 +010096 page = await browser.newPage()
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010097 await page.setViewport({
Marc Kupietz9e0f5192025-03-09 12:12:16 +010098 width: 1980,
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010099 height: 768,
100 deviceScaleFactor: 1,
Marc Kupietz964e7772025-06-03 15:02:30 +0200101 });
Marc Kupietz93d7f702025-06-27 15:41:48 +0200102
Marc Kupietzc4077822022-12-03 15:32:40 +0100103 })
104
Marc Kupietz93d7f702025-06-27 15:41:48 +0200105 after(async function() {
106 if (browser && typeof browser.close === 'function') {
107 await browser.close();
108 }
Marc Kupietzc4077822022-12-03 15:32:40 +0100109 })
110
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100111 afterEach(async function () {
112 if (this.currentTest.state == "failed") {
Marc Kupietz7f1666a2024-07-12 18:35:31 +0200113 if (slack) {
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200114 try {
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200115 slack.alert({
116 text: `🚨 Test on ${KORAP_URL} failed: *${this.currentTest.title}*`,
117 attachments: [{
118 color: 'danger',
119 fields: [{
120 title: 'Failed Test',
121 value: this.currentTest.title,
122 short: false
123 }, {
124 title: 'URL',
125 value: KORAP_URL,
126 short: true
127 }]
128 }]
129 });
130 } catch (slackError) {
131 console.error('Failed to send notification to Slack:', slackError.message);
132 }
133 }
134
Marc Kupietzd0ee97e2025-12-04 15:46:14 +0100135 // Send notification to Nextcloud Talk
136 if (nc_talk_conversation && nc_talk_secret) {
137 try {
138 const message = `🚨 Test on ${KORAP_URL} failed: **${this.currentTest.title}**`;
139 await sendToNextcloudTalk(message);
140 } catch (ncError) {
141 console.error('Failed to send notification to Nextcloud Talk:', ncError.message);
142 }
143 }
144
Marc Kupietz93d7f702025-06-27 15:41:48 +0200145 // Only take screenshot if it's not one of the initial connectivity/SSL tests
146 const initialTestTitles = [
147 'should be reachable',
148 'should have a valid SSL certificate'
149 ];
150 if (!initialTestTitles.includes(this.currentTest.title) && page) {
151 const screenshotPath = "failed_" + this.currentTest.title.replaceAll(/[ &\/]/g, "_") + '.png';
152 await page.screenshot({ path: screenshotPath });
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200153
Marc Kupietz93d7f702025-06-27 15:41:48 +0200154 const slackToken = process.env.SLACK_TOKEN;
155 if (slackToken) {
156 try {
157 const { WebClient } = require('@slack/web-api');
158 const fs = require('fs'); const web = new WebClient(slackToken);
159 const channelId = process.env.SLACK_CHANNEL_ID || 'C07CM4JS48H';
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200160
Marc Kupietz93d7f702025-06-27 15:41:48 +0200161 const result = await web.files.uploadV2({
162 channel_id: channelId,
163 file: fs.createReadStream(screenshotPath),
164 filename: screenshotPath,
165 title: `Screenshot: ${this.currentTest.title}`,
166 initial_comment: `📸 Screenshot of failed test: ${this.currentTest.title} on ${KORAP_URL}`
167 });
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200168
Marc Kupietz93d7f702025-06-27 15:41:48 +0200169 } catch (uploadError) {
170 console.error('Failed to upload screenshot to Slack:', uploadError.message);
171 }
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200172 }
Marc Kupietz7f1666a2024-07-12 18:35:31 +0200173 }
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100174 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200175 })
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100176
Marc Kupietz93d7f702025-06-27 15:41:48 +0200177 it('should be reachable', function (done) {
178 let doneCalled = false;
179 const url = new URL(KORAP_URL);
180 const httpModule = url.protocol === 'https:' ? https : require('http');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100181
Marc Kupietz93d7f702025-06-27 15:41:48 +0200182 const req = httpModule.request({
183 method: 'HEAD',
184 hostname: url.hostname,
185 port: url.port || (url.protocol === 'https:' ? 443 : 80),
186 path: url.pathname,
187 timeout: 5000
188 }, res => {
189 if (!doneCalled) {
190 doneCalled = true;
191 if (res.statusCode >= 200 && res.statusCode < 400) {
192 done();
193 } else {
194 done(new Error(`Server is not reachable. Status code: ${res.statusCode}`));
195 }
196 }
197 });
198 req.on('timeout', () => {
199 if (!doneCalled) {
200 doneCalled = true;
201 req.destroy();
202 done(new Error('Request to server timed out.'));
203 }
204 });
205 req.on('error', err => {
206 if (!doneCalled) {
207 doneCalled = true;
208 done(err);
209 }
210 });
211 req.end();
212 });
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100213
Marc Kupietz93d7f702025-06-27 15:41:48 +0200214 it('should have a valid SSL certificate', function (done) {
215 let doneCalled = false;
216 const url = new URL(KORAP_URL);
217 if (url.protocol !== 'https:') {
218 return this.skip();
219 }
220 const req = https.request({
221 method: 'HEAD',
222 hostname: url.hostname,
223 port: url.port || 443,
224 path: url.pathname,
225 timeout: 5000
226 }, res => {
227 if (!doneCalled) {
228 doneCalled = true;
229 const cert = res.socket.getPeerCertificate();
230 if (cert && cert.valid_to) {
231 const validTo = new Date(cert.valid_to);
232 if (validTo > new Date()) {
233 done();
234 } else {
235 done(new Error(`SSL certificate expired on ${validTo.toDateString()}`));
236 }
237 } else if (res.socket.isSessionReused()){
238 done();
239 }
240 else {
241 done(new Error('Could not retrieve SSL certificate information.'));
242 }
243 }
244 });
245 req.on('timeout', () => {
246 if (!doneCalled) {
247 doneCalled = true;
248 req.destroy();
249 done(new Error('Request to server timed out.'));
250 }
251 });
252 req.on('error', err => {
253 if (!doneCalled) {
254 doneCalled = true;
255 if (err.code === 'CERT_HAS_EXPIRED') {
256 done(new Error('SSL certificate has expired.'));
257 } else {
258 done(err);
259 }
260 }
261 });
262 req.end();
263 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100264
Marc Kupietz93d7f702025-06-27 15:41:48 +0200265 describe('UI Tests', function() {
Marc Kupietzc4077822022-12-03 15:32:40 +0100266
Marc Kupietz93d7f702025-06-27 15:41:48 +0200267 before(function() {
268 // Check the state of the parent suite's tests
269 const initialTests = this.test.parent.parent.tests;
270 if (initialTests[0].state === 'failed' || initialTests[1].state === 'failed') {
271 this.skip();
272 }
273 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100274
Marc Kupietz93d7f702025-06-27 15:41:48 +0200275
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200276
Marc Kupietz93d7f702025-06-27 15:41:48 +0200277 it('KorAP UI is up and running', async function () {
278 try {
279 await page.goto(KORAP_URL, { waitUntil: 'domcontentloaded' });
280 await page.waitForSelector("#q-field", { visible: true });
281 const query_field = await page.$("#q-field")
282 assert.isNotNull(query_field, "#q-field not found. Kalamar not running?");
283 } catch (error) {
284 throw new Error(`Failed to load KorAP UI or find query field: ${error.message}`);
285 }
Marc Kupietz0f6c54d2022-12-03 15:32:40 +0100286 })
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100287
Marc Kupietzc4077822022-12-03 15:32:40 +0100288
Marc Kupietz93d7f702025-06-27 15:41:48 +0200289 ifConditionIt('Login into KorAP with incorrect credentials fails',
290 KORAP_LOGIN != "",
291 (async () => {
292 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD + "*")
293 login_result.should.be.false
294 }))
295
296 ifConditionIt('Login into KorAP with correct credentials succeeds',
297 KORAP_LOGIN != "",
298 (async () => {
299 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD)
300 login_result.should.be.true
301 }))
302
303 it('Can turn glimpse off',
304 (async () => {
305 await korap_rc.assure_glimpse_off(page)
306 }))
307
308 it('Corpus statistics show sufficient tokens',
309 (async () => {
310 const tokenCount = await korap_rc.check_corpus_statistics(page, KORAP_MIN_TOKENS_IN_CORPUS);
311 console.log(`Found ${tokenCount} tokens in corpus, minimum required: ${KORAP_MIN_TOKENS_IN_CORPUS}`);
312 tokenCount.should.be.above(KORAP_MIN_TOKENS_IN_CORPUS - 1,
313 `Corpus should have at least ${KORAP_MIN_TOKENS_IN_CORPUS} tokens, but found ${tokenCount}`);
314 })).timeout(90000)
315
316 describe('Running searches that should have hits', () => {
317
318 before(async () => { await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD) })
319
320 KORAP_QUERIES.split(/[;,] */).forEach((query, i) => {
321 it('Search for "' + query + '" has hits',
322 (async () => {
323 await korap_rc.assure_glimpse_off(page)
324 const hits = await korap_rc.search(page, query)
325 hits.should.be.above(0)
326 })).timeout(20000)
327 })
328 })
329
330 ifConditionIt('Logout works',
331 KORAP_LOGIN != "",
332 (async () => {
333 const logout_result = await korap_rc.logout(page)
334 logout_result.should.be.true
335 })).timeout(15000)
336 });
337});