From 262400a92ea5ccf8ea914d9c71a02577ff8a3300 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Thu, 4 Nov 2021 16:43:14 -0400 Subject: [PATCH] test: migration from Python to JavaScript --- Tests/lib/Preferences.js | 2 +- Tests/lib/WebDAV.js | 70 ++++++++++++----- Tests/lib/utilities.js | 7 ++ Tests/package.json | 11 ++- Tests/spec/CalDAVPreventInvitationsSpec.js | 24 +++--- Tests/spec/MailDAVSpec.js | 88 ++++++++++++++++++++-- Tests/spec/SogoToolSpec.js | 24 ++++++ Tests/spec/support/jasmine.json | 3 + 8 files changed, 187 insertions(+), 42 deletions(-) create mode 100644 Tests/spec/SogoToolSpec.js diff --git a/Tests/lib/Preferences.js b/Tests/lib/Preferences.js index 43ba618cb..5849042a2 100644 --- a/Tests/lib/Preferences.js +++ b/Tests/lib/Preferences.js @@ -91,7 +91,7 @@ class Preferences { return obj } for (let k of Object.keys(obj)) { - if (typeof obj[k] == 'object') { + if (obj[k] && typeof obj[k] == 'object') { let o = this.findKey(obj[k], key) if (o !== null) return o diff --git a/Tests/lib/WebDAV.js b/Tests/lib/WebDAV.js index 4c77be5c8..e8ed12abd 100644 --- a/Tests/lib/WebDAV.js +++ b/Tests/lib/WebDAV.js @@ -22,8 +22,22 @@ import config from './config' const DAVInverse = 'urn:inverse:params:xml:ns:inverse-dav' const DAVInverseShort = 'i' +const DAVMailHeader = 'urn:schemas:mailheader:' +const DAVMailHeaderShort = 'mh' +const DAVHttpMail = 'urn:schemas:httpmail:' +const DAVHttpMailShort = 'hm' +const DAVnsShortMap = { + [DAVInverse]: DAVInverseShort, + [DAVMailHeader]: DAVMailHeaderShort, + [DAVHttpMail]: DAVHttpMailShort, + ...DAVNamespaceShorthandMap +} -export { DAVInverse, DAVInverseShort } +export { + DAVInverse, DAVInverseShort, + DAVMailHeader, DAVMailHeaderShort, + DAVHttpMail, DAVHttpMailShort +} class WebDAV { constructor(un, pw) { @@ -97,15 +111,20 @@ class WebDAV { } propfindWebdav(resource, properties, namespace = DAVNamespace.DAV, headers = {}) { - const nsShort = DAVNamespaceShorthandMap[namespace] || DAVInverseShort + const nsShort = DAVnsShortMap[namespace] || DAVInverseShort const formattedProperties = properties.map(p => { return { [`${nsShort}:${p}`]: '' } }) + let url + if (resource.match(/^http/)) + url = resource + else + url = this.serverUrl + resource if (typeof headers.depth == 'undefined') { headers.depth = new String(0) } return davRequest({ - url: this.serverUrl + resource, + url, init: { method: 'PROPFIND', headers: { ...this.headers, ...headers }, @@ -427,27 +446,38 @@ class WebDAV { }) } - mailQueryMaildav(resource, properties, filters = {}) { + mailQueryMaildav(resource, properties, filters = {}, sort, ascending = true) { let formattedFilters = {} - if (filters.constructor.toString().includes('Array')) { - filters.map(f => { - Object.keys(f).map(p => { + if (filters) { + if (filters.constructor.toString().includes('Array')) { + filters.map(f => { + Object.keys(f).map(p => { + const pName = `${DAVInverseShort}:${p}` + if (!formattedFilters[pName]) + formattedFilters[pName] = [] + formattedFilters[pName].push({ _attributes: f[p] }) + }) + }) + } + else { + Object.keys(filters).map(p => { const pName = `${DAVInverseShort}:${p}` if (!formattedFilters[pName]) formattedFilters[pName] = [] - formattedFilters[pName].push({ _attributes: f[p] }) + formattedFilters[pName].push({ _attributes: filters[p] }) }) - }) + } + if (Object.keys(formattedFilters).length) { + formattedFilters = {[`${DAVInverseShort}:mail-filters`]: formattedFilters} + } } - else { - Object.keys(filters).map(p => { - const pName = `${DAVInverseShort}:${p}` - if (!formattedFilters[pName]) - formattedFilters[pName] = [] - formattedFilters[pName].push({ _attributes: filters[p] }) - }) + let formattedSort = {} + if (sort) { + formattedSort = {[`${DAVInverseShort}:sort`]: { + _attributes: { order: ascending ? 'ascending' : 'descending' }, + [sort]: {} + }} } - return davRequest({ url: this.serverUrl + resource, init: { @@ -458,10 +488,12 @@ class WebDAV { [`${DAVInverseShort}:mail-query`]: { _attributes: { ...getDAVAttribute([DAVNamespace.DAV]), - [`xmlns:${DAVInverseShort}`]: DAVInverse + [`xmlns:${DAVInverseShort}`]: DAVInverse, + [`xmlns:${DAVMailHeaderShort}`]: DAVMailHeader }, prop: formatProps(properties.map(p => { return { name: p } })), - [`${DAVInverseShort}:mail-filters`]: formattedFilters + ...formattedFilters, + ...formattedSort } } } diff --git a/Tests/lib/utilities.js b/Tests/lib/utilities.js index 2440186bc..7e2bb524a 100644 --- a/Tests/lib/utilities.js +++ b/Tests/lib/utilities.js @@ -47,6 +47,13 @@ class TestUtility { return s; } + camelCase(snakeCase) { + return snakeCase.replace(/(?:^\w|[A-Z]|\b\w)/g, (char, i) => + i === 0 ? char.toLowerCase() : char.toUpperCase(), + ) + .replace(/[\s\-_]+/g, '') + } + setupRights(resource, username, rights) { const action = (typeof rights == 'undefined') ? 'remove-user' : 'set-roles' return davRequest({ diff --git a/Tests/package.json b/Tests/package.json index e85967127..556063ff3 100644 --- a/Tests/package.json +++ b/Tests/package.json @@ -1,20 +1,19 @@ { "name": "tests", "description": "This directory holds automated tests for SOGo.", - "type": "module", "devDependencies": {}, "scripts": { - "test": "jasmine --require=esm" + "test": "jasmine" }, "dependencies": { - "@babel/core": "^7.15.0", - "@babel/preset-env": "^7.15.0", + "@babel/core": "^7.15.8", + "@babel/preset-env": "^7.15.8", "babel-cli": "^6.26.0", "cookie": "^0.4.1", "cross-fetch": "^3.1.4", "esm": "^3.2.25", "ical.js": "^1.4.0", - "jasmine": "^3.8.0", - "tsdav": "^1.0.6" + "jasmine": "^3.10.0", + "tsdav": "^1.1.1" } } diff --git a/Tests/spec/CalDAVPreventInvitationsSpec.js b/Tests/spec/CalDAVPreventInvitationsSpec.js index 7614dc539..421399ec8 100644 --- a/Tests/spec/CalDAVPreventInvitationsSpec.js +++ b/Tests/spec/CalDAVPreventInvitationsSpec.js @@ -13,11 +13,13 @@ let utility, user, attendee1, attendee1Delegate let userCalendar, attendee1Calendar, attendee1DelegateCalendar let icsName, icsList, vcalendar -describe('PreventInvitationsWhitelist user setting', function() { +describe('PreventInvitations', function() { const _getEvent = async function(client, calendarName, filename, expectedCode = 200) { const [{ status, headers, raw }] = await client.getObject(calendarName, filename) - expect(status).toBe(expectedCode) + expect(status) + .withContext(`HTTP status code when fetching event ${calendarName}${filename}`) + .toBe(expectedCode) if (status <= 300) return new ICAL.Component(ICAL.parse(raw)) return false @@ -26,7 +28,7 @@ describe('PreventInvitationsWhitelist user setting', function() { const _putEvent = async function(client, calendarName, filename, event, expectedCode = 201) { const response = await client.createCalendarObject(calendarName, filename, event.toString()) expect(response.status) - .withContext(`Create event ${calendarName}${filename}`) + .withContext(`HTTP status code when creating event ${calendarName}${filename}`) .toBe(expectedCode) return response } @@ -95,12 +97,12 @@ describe('PreventInvitationsWhitelist user setting', function() { utility = new TestUtility(webdav) user = await utility.fetchUserInfo(config.username) - attendee1 = await utility.fetchUserInfo(config.attendee1) - attendee1Delegate = await utility.fetchUserInfo(config.attendee1_delegate) + attendee1 = await utility.fetchUserInfo(config.attendee1_username) + attendee1Delegate = await utility.fetchUserInfo(config.attendee1_delegate_password) userCalendar = `/SOGo/dav/${config.username}/Calendar/personal/` - attendee1Calendar = `/SOGo/dav/${config.attendee1}/Calendar/personal/` - attendee1DelegateCalendar = `/SOGo/dav/${config.attendee1_delegate}/Calendar/personal/` + attendee1Calendar = `/SOGo/dav/${config.attendee1_username}/Calendar/personal/` + attendee1DelegateCalendar = `/SOGo/dav/${config.attendee1_delegate_username}/Calendar/personal/` // fetch non existing event to let sogo create the calendars in the db await _getEvent(webdav, userCalendar, 'nonexistent', 404) @@ -123,7 +125,7 @@ describe('PreventInvitationsWhitelist user setting', function() { } }) - it(`Set/get the PreventInvitation pref`, async function() { + it(`Disable, accept the invitation`, async function() { // First accept the invitation await prefs.set('PreventInvitations', 0) const settings = await prefs.getSettings() @@ -135,7 +137,7 @@ describe('PreventInvitationsWhitelist user setting', function() { await _verifyEvent() }) - it(`Set PreventInvitation and don't accept the Invitation`, async function() { + it(`Enable, refuse the invitation`, async function() { // Second, enable PreventInviation and refuse it await prefs.set('PreventInvitations', 1) const settings = await prefs.getSettings() @@ -147,7 +149,7 @@ describe('PreventInvitationsWhitelist user setting', function() { await _verifyEvent(404) }) - it(`Set PreventInvitation add to WhiteList and accept the Invitation`, async function() { + it(`Enable, update whitelist, accept the invitation`, async function() { // First, add the Organiser to the Attendee's whitelist await prefs.set('PreventInvitations', 1) await prefs.set('PreventInvitationsWhitelist', config.white_listed_attendee) @@ -157,7 +159,7 @@ describe('PreventInvitationsWhitelist user setting', function() { .withContext(`Prevent invitations is enabled`) .toBe(1) expect(PreventInvitationsWhitelist) - .withContext(`Prevent invitations is enabled, one user is whitelisted`) + .withContext(`Prevent invitations is enabled, one user (${config.white_listed_attendee}) is whitelisted`) .toEqual(config.white_listed_attendee) // Second, try again to invite, it should work await _addAttendee() diff --git a/Tests/spec/MailDAVSpec.js b/Tests/spec/MailDAVSpec.js index d5f80c267..3358ea440 100644 --- a/Tests/spec/MailDAVSpec.js +++ b/Tests/spec/MailDAVSpec.js @@ -1,7 +1,8 @@ import config from '../lib/config' -import WebDAV from '../lib/WebDAV' +import { default as WebDAV, DAVMailHeaderShort, DAVHttpMail, DAVMailHeader } from '../lib/WebDAV' import TestUtility from '../lib/utilities' import { fetch } from 'cross-fetch' +import { DAVNamespace, DAVNamespaceShorthandMap } from 'tsdav' const message1 = `Return-Path: Received: from cyril.dev (localhost [127.0.0.1]) @@ -168,7 +169,8 @@ describe('MailDAV', function() { const _makeMailbox = async function (path, expectedCode = 201) { const [lastFolder, ...parents] = path.split('/').reverse() let mailPath = lastFolder - if (parents.lenght) { + if (parents.length) { + // Prefix parent names with "folder" mailPath = parents.reverse().map(p => `folder${p}`).join('/') + '/' + lastFolder } const [response] = await webdav.makeCollection(resource + mailPath) @@ -217,17 +219,51 @@ describe('MailDAV', function() { .toEqual(hrefs.length) } + const _testSort = async function (sortAttribute, expectedHrefs, ascending = true) { + const url = `${resource}foldertest-dav-mail` + const results = await webdav.mailQueryMaildav(url, ['displayname'], null, sortAttribute, ascending) + + let received_count = 0 + for (let i = 0; i < results.length; i++) { + let response = results[i] + expect(response.status) + .withContext(`HTTP status code when performing a mail sorting query`) + .toBe(207) + if (response.href) { + expect(response.href) + .withContext(`Sort result at position ${i} on attribute ${sortAttribute}`) + .toEqual(expectedHrefs[i]) + received_count++ + } + } + expect(received_count) + .withContext(`Expected number of results from mail sorting query by ${sortAttribute}`) + .toEqual(expectedHrefs.length) + } + + const _testProperty = async function (url, namespace, property, expected) { + const [result] = await webdav.propfindWebdav(url, [property], namespace) + const { props: { [utility.camelCase(property)]: objectProperty }} = result + + expect(objectProperty) + .withContext(`Property ${utility.camelCase(property)} of ${url}`) + .toEqual(expected) + } + beforeAll(async function() { webdav = new WebDAV(config.username, config.password) utility = new TestUtility(webdav) user = await utility.fetchUserInfo(config.username) - mailboxesList = [] resource = `/SOGo/dav/${config.username}/Mail/0/` }) + beforeEach(function() { + mailboxesList = [] + }) + afterEach(async function() { for (let path of mailboxesList.reverse()) { - await _deleteMailbox(path) + await _deleteMailbox(path, null) } }) @@ -261,7 +297,7 @@ describe('MailDAV', function() { .toBe(200) }) - fit(`mail-query filters`, async function() { + it(`mail-query filters`, async function() { const mailbox = 'test-dav-mail' const url = `${resource}folder${mailbox}` let msg1Loc, msg2Loc, msg3Loc @@ -529,4 +565,46 @@ describe('MailDAV', function() { } }, 30000) // increase timeout for this long test + + it(`mail-query sort`, async function() { + const mailbox = 'test-dav-mail' + const url = `${resource}folder${mailbox}` + let msg1Loc, msg2Loc, msg3Loc + let filter, filters + + await _makeMailbox(mailbox) + msg1Loc = await _putMessage(mailbox, message1); + msg2Loc = await _putMessage(mailbox, message2); + msg3Loc = await _putMessage(mailbox, message3); + + await _testSort(`${DAVMailHeaderShort}:received`, [msg1Loc, msg2Loc, msg3Loc]) + await _testSort(`${DAVMailHeaderShort}:date`, [ msg2Loc, msg1Loc, msg3Loc ]) + await _testSort(`${DAVMailHeaderShort}:from`, [ msg1Loc, msg2Loc, msg3Loc ]) + await _testSort(`${DAVMailHeaderShort}:to`, [ msg1Loc, msg2Loc, msg3Loc ]) + await _testSort(`${DAVMailHeaderShort}:cc`, [ msg3Loc, msg1Loc, msg2Loc ]) + await _testSort(`${DAVMailHeaderShort}:subject`, [ msg3Loc, msg1Loc, msg2Loc ]) + await _testSort(`${DAVNamespaceShorthandMap[DAVNamespace.DAV]}:getcontentlength`, [ msg3Loc, msg1Loc, msg2Loc ]) + await _testSort(`${DAVMailHeaderShort}:cc`, [ msg2Loc, msg1Loc, msg3Loc ], false) + + }, 30000) // increase timeout for this long test + + it(`message properties`, async function() { + const mailbox = 'test-dav-mail' + await _makeMailbox(mailbox) + const msg1Loc = await _putMessage(mailbox, message1); + + await _testProperty(msg1Loc, DAVHttpMail, 'date', 'Mon, 28 Sep 2009 11:42:14 GMT') + await _testProperty(msg1Loc, DAVHttpMail, 'hasattachment', 0) + await _testProperty(msg1Loc, DAVHttpMail, 'read', 0) + await _testProperty(msg1Loc, DAVHttpMail, 'textdescription', ``) + await _testProperty(msg1Loc, DAVHttpMail, 'unreadcount', {}) + await _testProperty(msg1Loc, DAVMailHeader, 'cc', '2message1cc@cyril.dev, user10@cyril.dev') + await _testProperty(msg1Loc, DAVMailHeader, 'date', 'Mon, 28 Sep 2009 11:42:14 GMT') + await _testProperty(msg1Loc, DAVMailHeader, 'from', 'Cyril ') + await _testProperty(msg1Loc, DAVMailHeader, 'in-reply-to', {}) + await _testProperty(msg1Loc, DAVMailHeader, 'message-id', '<4AC1F29sept6.5060801@cyril.dev>') + await _testProperty(msg1Loc, DAVMailHeader, 'references', '<4AC3BF1B.3010806@inverse.ca>') + await _testProperty(msg1Loc, DAVMailHeader, 'subject', 'message1subject') + await _testProperty(msg1Loc, DAVMailHeader, 'to', 'message1to@cyril.dev') + }) }) \ No newline at end of file diff --git a/Tests/spec/SogoToolSpec.js b/Tests/spec/SogoToolSpec.js new file mode 100644 index 000000000..086fe8180 --- /dev/null +++ b/Tests/spec/SogoToolSpec.js @@ -0,0 +1,24 @@ +import config from '../lib/config' +import { mkdtempSync, rmSync } from 'fs' + +const os = require('os') +const path = require('path') +const { execSync } = require('child_process') + +describe('sogo-tool tests', function() { + let tmpdir + + beforeEach(function() { + tmpdir = mkdtempSync(path.join(os.tmpdir(), 'sogo-')) + }) + + afterEach(function() { + rmSync(tmpdir, { recursive: true, force: true }) + }) + + it('backup', async function() { + execSync(`sogo-tool backup ${tmpdir} ${config.username}`, (error, stdout, stderr) => { + expect(error).not.toBeDefined() + }) + }) +}) \ No newline at end of file diff --git a/Tests/spec/support/jasmine.json b/Tests/spec/support/jasmine.json index 4f0fdb35c..96b797278 100644 --- a/Tests/spec/support/jasmine.json +++ b/Tests/spec/support/jasmine.json @@ -6,6 +6,9 @@ "helpers": [ "helpers/**/*.?(m)js" ], + "requires": [ + "esm" + ], "stopSpecOnExpectationFailure": false, "random": true }