diff --git a/Gruntfile.js b/Gruntfile.js index 20079953..6547588b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -180,8 +180,8 @@ module.exports = function (grunt) { grunt.registerTask('compile-server', ['run:lint-server', 'run:compile-server']) grunt.registerTask('compile-client', ['run:lint-client', 'run:compile-client']) - grunt.registerTask('test-server', ['env:env-test-server-unit', 'run:test-server-unit']) - grunt.registerTask('test-client', ['env:env-test-client-unit', 'run:test-client-unit']) + grunt.registerTask('test-server', ['run:test-server-unit']) + grunt.registerTask('test-client', ['run:test-client-unit']) grunt.registerTask('test-unit', ['test-server', 'test-client']); grunt.registerTask('test-int', ['run:test-int']); diff --git a/config.template.yml b/config.template.yml index 5927551c..7c013519 100644 --- a/config.template.yml +++ b/config.template.yml @@ -209,7 +209,8 @@ storage: # Settings to connect to mongo server mongo: - url: mongodb://mongo/authelia + url: mongodb://mongo + database: authelia # Configuration of the notification system. # diff --git a/example/authelia/docker-compose.test.yml b/example/authelia/docker-compose.test.yml index 53495d0f..2d2899a8 100644 --- a/example/authelia/docker-compose.test.yml +++ b/example/authelia/docker-compose.test.yml @@ -3,3 +3,7 @@ services: authelia: volumes: - ./config.test.yml:/etc/authelia/config.yml:ro + - ./dist/server:/usr/src/server + - ./dist/shared:/usr/src/shared + networks: + - example-network diff --git a/package-lock.json b/package-lock.json index ef426e50..7d324c77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5045,17 +5045,17 @@ "optional": true }, "mongodb": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.4.tgz", - "integrity": "sha512-90YIIs7A4ko4kCGafxxXj3foexCAlJBC0YLwwIKgSLoE7Vni2IqUMz6HSsZ3zbXOfR1KWtxfnc0RyAMAY/ViLg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.5.tgz", + "integrity": "sha512-8ioTyyc8tkNwZCTDa1FPWvmpJFfvE484DnugC8KpVrw4AKAE03OOAlORl2yYTNtz3TX4Ab7FRo00vzgexB/67A==", "requires": { - "mongodb-core": "3.0.4" + "mongodb-core": "3.0.5" } }, "mongodb-core": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.4.tgz", - "integrity": "sha512-OTH267FjfwBdEufSnrgd+u8HuLWRuQ6p8DR0XirPl2BdlLEMh4XwjJf1RTlruILp5p2m1w8dDC8rCxibC3W8qQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.5.tgz", + "integrity": "sha512-4A1nx/xAU5d/NPICjiyzVxzNrIdJQQsYRe3xQkV1O638t+fHHfAOLK+SQagqGnu1m0aeSxb1ixp/P0FGSQWIGA==", "requires": { "bson": "1.0.6", "require_optional": "1.0.1" diff --git a/package.json b/package.json index a8d52fa5..dd4b55d6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "authelia": "./dist/server/src/index.js" }, "scripts": { - "test": "./node_modules/.bin/grunt unit-tests", + "test": "./node_modules/.bin/grunt test-unit", "cover": "NODE_ENV=test nyc npm t", "serve": "node dist/server/index.js" }, @@ -33,7 +33,7 @@ "express-request-id": "^1.4.0", "express-session": "^1.14.2", "ldapjs": "^1.0.2", - "mongodb": "^3.0.4", + "mongodb": "^3.0.5", "nedb": "^1.8.0", "nodemailer": "^4.0.1", "nodemailer-direct-transport": "^3.3.2", @@ -53,7 +53,7 @@ "@types/bootstrap": "^4.0.1", "@types/connect-redis": "0.0.7", "@types/cors": "^2.8.1", - "@types/cucumber": "^2.3.1", + "@types/cucumber": "^4.0.1", "@types/ejs": "^2.3.33", "@types/express": "^4.0.35", "@types/express-session": "1.15.8", diff --git a/scripts/run-cucumber.sh b/scripts/run-cucumber.sh index af64ff08..cfd0ed9f 100755 --- a/scripts/run-cucumber.sh +++ b/scripts/run-cucumber.sh @@ -1,3 +1,5 @@ #!/bin/bash -./node_modules/.bin/cucumber-js --colors --compiler ts:ts-node/register $* +REQ=`for f in test/features/step_definitions/*.ts; do echo "--require $f"; done;` + +./node_modules/.bin/cucumber-js --format-options '{"colorsEnabled": true}' --require-module ts-node/register --require test/features/support/world.ts $REQ $* diff --git a/server/src/index.ts b/server/src/index.ts index 286da1fc..fcbf4d02 100755 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -20,7 +20,8 @@ const deps: GlobalDependencies = { winston: require("winston"), speakeasy: require("speakeasy"), nedb: require("nedb"), - ConnectRedis: require("connect-redis") + ConnectRedis: require("connect-redis"), + Redis: require("redis") }; const server = new Server(deps); diff --git a/server/src/lib/ServerVariablesInitializer.ts b/server/src/lib/ServerVariablesInitializer.ts index 46c91ff1..9eed50f1 100644 --- a/server/src/lib/ServerVariablesInitializer.ts +++ b/server/src/lib/ServerVariablesInitializer.ts @@ -52,7 +52,7 @@ class UserDataStoreFactory { else if (config.storage.mongo) { const mongoConnectorFactory = new MongoConnectorFactory(); const mongoConnector = mongoConnectorFactory.create(config.storage.mongo.url); - return mongoConnector.connect() + return mongoConnector.connect(config.storage.mongo.database) .then(function (client: IMongoClient) { const collectionFactory = CollectionFactoryFactory.createMongo(client); return BluebirdPromise.resolve(new UserDataStore(collectionFactory)); diff --git a/server/src/lib/configuration/Configuration.d.ts b/server/src/lib/configuration/Configuration.d.ts index fca4547a..995c4705 100644 --- a/server/src/lib/configuration/Configuration.d.ts +++ b/server/src/lib/configuration/Configuration.d.ts @@ -95,6 +95,7 @@ export interface NotifierConfiguration { export interface MongoStorageConfiguration { url: string; + database: string; } export interface LocalStorageConfiguration { diff --git a/server/src/lib/configuration/SessionConfigurationBuilder.ts b/server/src/lib/configuration/SessionConfigurationBuilder.ts index bee21c76..fa5e4443 100644 --- a/server/src/lib/configuration/SessionConfigurationBuilder.ts +++ b/server/src/lib/configuration/SessionConfigurationBuilder.ts @@ -2,7 +2,6 @@ import ExpressSession = require("express-session"); import { AppConfiguration } from "./Configuration"; import { GlobalDependencies } from "../../../types/Dependencies"; -import Redis = require("redis"); export class SessionConfigurationBuilder { @@ -23,7 +22,7 @@ export class SessionConfigurationBuilder { let redisOptions; if (configuration.session.redis.host && configuration.session.redis.port) { - const client = Redis.createClient({ + const client = deps.Redis.createClient({ host: configuration.session.redis.host, port: configuration.session.redis.port }); diff --git a/server/src/lib/configuration/Validator.ts b/server/src/lib/configuration/Validator.ts index fecc02c7..307ed2bb 100644 --- a/server/src/lib/configuration/Validator.ts +++ b/server/src/lib/configuration/Validator.ts @@ -13,7 +13,7 @@ function validateSchema(configuration: UserConfiguration): string[] { allErrors: true, missingRefs: "fail" }); - ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-04.json")); + ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-06.json")); const valid = ajv.validate(schema, configuration); if (!valid) return ajv.errors.map( @@ -21,14 +21,15 @@ function validateSchema(configuration: UserConfiguration): string[] { return []; } +function diff(a: string[], b: string[]) { + return a.filter(function(i) {return b.indexOf(i) < 0; }); +} + function validateUnknownKeys(path: string, obj: any, knownKeys: string[]) { - const keysSet = new Set(Object.keys(obj)); - const knownKeysSet = new Set(knownKeys); + const keysSet = Object.keys(obj); - const unknownKeysSet = new Set( - [...keysSet].filter(x => !knownKeysSet.has(x))); - - if (unknownKeysSet.size > 0) { + const unknownKeysSet = diff(keysSet, knownKeys); + if (unknownKeysSet.length > 0) { const unknownKeys = Array.from(unknownKeysSet); return unknownKeys.map((k: string) => { return Util.format("data.%s has unknown key '%s'", path, k); }); } diff --git a/server/src/lib/connectors/mongo/IMongoConnector.d.ts b/server/src/lib/connectors/mongo/IMongoConnector.d.ts index 8ec4cda1..d3403f0a 100644 --- a/server/src/lib/connectors/mongo/IMongoConnector.d.ts +++ b/server/src/lib/connectors/mongo/IMongoConnector.d.ts @@ -2,5 +2,6 @@ import BluebirdPromise = require("bluebird"); import { IMongoClient } from "./IMongoClient"; export interface IMongoConnector { - connect(): BluebirdPromise; + connect(databaseName: string): BluebirdPromise; + close(): BluebirdPromise; } \ No newline at end of file diff --git a/server/src/lib/connectors/mongo/MongoConnector.ts b/server/src/lib/connectors/mongo/MongoConnector.ts index 6f3bcf48..e6a82d4b 100644 --- a/server/src/lib/connectors/mongo/MongoConnector.ts +++ b/server/src/lib/connectors/mongo/MongoConnector.ts @@ -7,16 +7,25 @@ import { MongoClient } from "./MongoClient"; export class MongoConnector implements IMongoConnector { private url: string; + private client: MongoDB.MongoClient; constructor(url: string) { this.url = url; } - connect(): BluebirdPromise { + connect(databaseName: string): BluebirdPromise { + const that = this; const connectAsync = BluebirdPromise.promisify(MongoDB.MongoClient.connect); return connectAsync(this.url) - .then(function (db: MongoDB.Db) { + .then(function (client: MongoDB.MongoClient) { + that.client = client; + const db = client.db(databaseName); return BluebirdPromise.resolve(new MongoClient(db)); }); } + + close(): BluebirdPromise { + this.client.close(); + return BluebirdPromise.resolve(); + } } \ No newline at end of file diff --git a/server/test/ServerConfiguration.test.ts b/server/test/ServerConfiguration.test.ts index 2b8ac0a8..7cb6bbce 100644 --- a/server/test/ServerConfiguration.test.ts +++ b/server/test/ServerConfiguration.test.ts @@ -75,6 +75,7 @@ describe("test server configuration", function () { .then(function () { Assert(sessionMock.calledOnce); Assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com"); + server.stop(); }); }); }); diff --git a/server/test/SessionConfigurationBuilder.test.ts b/server/test/SessionConfigurationBuilder.test.ts index 037c90a0..2d30aa8f 100644 --- a/server/test/SessionConfigurationBuilder.test.ts +++ b/server/test/SessionConfigurationBuilder.test.ts @@ -141,6 +141,7 @@ describe("test session configuration builder", function () { }; const RedisStoreMock = Sinon.spy(); + const redisClient = Sinon.mock().returns({ on: Sinon.spy() }); const deps: GlobalDependencies = { ConnectRedis: Sinon.stub().returns(RedisStoreMock) as any, @@ -149,7 +150,10 @@ describe("test session configuration builder", function () { session: Sinon.spy() as any, speakeasy: Sinon.spy() as any, u2f: Sinon.spy() as any, - winston: Sinon.spy() as any + winston: Sinon.spy() as any, + Redis: { + createClient: Sinon.mock().returns(redisClient) + } as any }; const options = SessionConfigurationBuilder.build(configuration, deps); diff --git a/server/test/connectors/mongo/MongoConnector.test.ts b/server/test/connectors/mongo/MongoConnector.test.ts index 072bcd83..8aebaa9b 100644 --- a/server/test/connectors/mongo/MongoConnector.test.ts +++ b/server/test/connectors/mongo/MongoConnector.test.ts @@ -7,6 +7,7 @@ import { MongoConnector } from "../../../src/lib/connectors/mongo/MongoConnector describe("MongoConnector", function () { let mongoClientConnectStub: Sinon.SinonStub; + describe("create", function () { before(function () { mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect"); @@ -17,11 +18,12 @@ describe("MongoConnector", function () { }); it("should create a connector", function () { - mongoClientConnectStub.yields(undefined); + const client = { db: Sinon.mock() }; + mongoClientConnectStub.yields(undefined, client); const url = "mongodb://test.url"; const connector = new MongoConnector(url); - return connector.connect() + return connector.connect("database") .then(function (client: IMongoClient) { Assert(client); Assert(mongoClientConnectStub.calledWith(url)); @@ -33,7 +35,7 @@ describe("MongoConnector", function () { const url = "mongodb://test.url"; const connector = new MongoConnector(url); - return connector.connect() + return connector.connect("database") .then(function () { return BluebirdPromise.reject(new Error("It should not be here")); }) .error(function (client: IMongoClient) { Assert(client); diff --git a/server/types/Dependencies.ts b/server/types/Dependencies.ts index 47c5454a..f20404db 100644 --- a/server/types/Dependencies.ts +++ b/server/types/Dependencies.ts @@ -6,6 +6,7 @@ import nedb = require("nedb"); import ldapjs = require("ldapjs"); import u2f = require("u2f"); import RedisSession = require("connect-redis"); +import Redis = require("redis"); export type Speakeasy = typeof speakeasy; export type Winston = typeof winston; @@ -14,11 +15,13 @@ export type Nedb = typeof nedb; export type Ldapjs = typeof ldapjs; export type U2f = typeof u2f; export type ConnectRedis = typeof RedisSession; +export type Redis = typeof Redis; export interface GlobalDependencies { u2f: U2f; ldapjs: Ldapjs; session: Session; + Redis: Redis; ConnectRedis: ConnectRedis; winston: Winston; speakeasy: Speakeasy; diff --git a/test/features/access-control.feature b/test/features/access-control.feature index 197619fa..9b5cb076 100644 --- a/test/features/access-control.feature +++ b/test/features/access-control.feature @@ -7,20 +7,16 @@ Feature: User has access restricted access to domains And I use "REGISTERED" as TOTP token handle And I click on "Sign in" And I'm redirected to "https://home.example.com:8080/" - Then I have access to: - | url | - | https://public.example.com:8080/secret.html | - | https://dev.example.com:8080/groups/admin/secret.html | - | https://dev.example.com:8080/groups/dev/secret.html | - | https://dev.example.com:8080/users/john/secret.html | - | https://dev.example.com:8080/users/harry/secret.html | - | https://dev.example.com:8080/users/bob/secret.html | - | https://admin.example.com:8080/secret.html | - | https://mx1.mail.example.com:8080/secret.html | - | https://single_factor.example.com:8080/secret.html | - And I have no access to: - | url | - | https://mx2.mail.example.com:8080/secret.html | + Then I have access to "https://public.example.com:8080/secret.html" + And I have access to "https://dev.example.com:8080/groups/admin/secret.html" + And I have access to "https://dev.example.com:8080/groups/dev/secret.html" + And I have access to "https://dev.example.com:8080/users/john/secret.html" + And I have access to "https://dev.example.com:8080/users/harry/secret.html" + And I have access to "https://dev.example.com:8080/users/bob/secret.html" + And I have access to "https://admin.example.com:8080/secret.html" + And I have access to "https://mx1.mail.example.com:8080/secret.html" + And I have access to "https://single_factor.example.com:8080/secret.html" + And I have no access to "https://mx2.mail.example.com:8080/secret.html" @need-registered-user-bob Scenario: User bob has restricted access @@ -29,20 +25,16 @@ Feature: User has access restricted access to domains And I use "REGISTERED" as TOTP token handle And I click on "Sign in" And I'm redirected to "https://home.example.com:8080/" - Then I have access to: - | url | - | https://public.example.com:8080/secret.html | - | https://dev.example.com:8080/groups/dev/secret.html | - | https://dev.example.com:8080/users/bob/secret.html | - | https://mx1.mail.example.com:8080/secret.html | - | https://mx2.mail.example.com:8080/secret.html | - And I have no access to: - | url | - | https://dev.example.com:8080/groups/admin/secret.html | - | https://admin.example.com:8080/secret.html | - | https://dev.example.com:8080/users/john/secret.html | - | https://dev.example.com:8080/users/harry/secret.html | - | https://single_factor.example.com:8080/secret.html | + Then I have access to "https://public.example.com:8080/secret.html" + And I have no access to "https://dev.example.com:8080/groups/admin/secret.html" + And I have access to "https://dev.example.com:8080/groups/dev/secret.html" + And I have no access to "https://dev.example.com:8080/users/john/secret.html" + And I have no access to "https://dev.example.com:8080/users/harry/secret.html" + And I have access to "https://dev.example.com:8080/users/bob/secret.html" + And I have no access to "https://admin.example.com:8080/secret.html" + And I have access to "https://mx1.mail.example.com:8080/secret.html" + And I have no access to "https://single_factor.example.com:8080/secret.html" + And I have access to "https://mx2.mail.example.com:8080/secret.html" @need-registered-user-harry Scenario: User harry has restricted access @@ -51,17 +43,13 @@ Feature: User has access restricted access to domains And I use "REGISTERED" as TOTP token handle And I click on "Sign in" And I'm redirected to "https://home.example.com:8080/" - Then I have access to: - | url | - | https://public.example.com:8080/secret.html | - | https://dev.example.com:8080/users/harry/secret.html | - And I have no access to: - | url | - | https://dev.example.com:8080/groups/dev/secret.html | - | https://dev.example.com:8080/users/bob/secret.html | - | https://dev.example.com:8080/groups/admin/secret.html | - | https://admin.example.com:8080/secret.html | - | https://dev.example.com:8080/users/john/secret.html | - | https://mx1.mail.example.com:8080/secret.html | - | https://mx2.mail.example.com:8080/secret.html | - | https://single_factor.example.com:8080/secret.html | \ No newline at end of file + Then I have access to "https://public.example.com:8080/secret.html" + And I have no access to "https://dev.example.com:8080/groups/admin/secret.html" + And I have no access to "https://dev.example.com:8080/groups/dev/secret.html" + And I have no access to "https://dev.example.com:8080/users/john/secret.html" + And I have access to "https://dev.example.com:8080/users/harry/secret.html" + And I have no access to "https://dev.example.com:8080/users/bob/secret.html" + And I have no access to "https://admin.example.com:8080/secret.html" + And I have no access to "https://mx1.mail.example.com:8080/secret.html" + And I have no access to "https://single_factor.example.com:8080/secret.html" + And I have no access to "https://mx2.mail.example.com:8080/secret.html" diff --git a/test/features/reset-password.feature b/test/features/reset-password.feature index 33cf93b1..d449fad7 100644 --- a/test/features/reset-password.feature +++ b/test/features/reset-password.feature @@ -1,24 +1,24 @@ Feature: User is able to reset his password Scenario: User is redirected to password reset page - Given I'm on https://login.example.com:8080 + Given I'm on "https://login.example.com:8080" When I click on the link "Forgot password?" Then I'm redirected to "https://login.example.com:8080/password-reset/request" Scenario: User get an email with a link to reset password - Given I'm on https://login.example.com:8080/password-reset/request + Given I'm on "https://login.example.com:8080/password-reset/request" When I set field "username" to "james" And I click on "Reset Password" Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password." Scenario: Request password for unexisting user should behave like existing user - Given I'm on https://login.example.com:8080/password-reset/request + Given I'm on "https://login.example.com:8080/password-reset/request" When I set field "username" to "fake_user" And I click on "Reset Password" Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password." Scenario: User resets his password - Given I'm on https://login.example.com:8080/password-reset/request + Given I'm on "https://login.example.com:8080/password-reset/request" And I set field "username" to "james" And I click on "Reset Password" When I click on the link of the email @@ -29,7 +29,7 @@ Feature: User is able to reset his password Scenario: User does not confirm new password - Given I'm on https://login.example.com:8080/password-reset/request + Given I'm on "https://login.example.com:8080/password-reset/request" And I set field "username" to "james" And I click on "Reset Password" When I click on the link of the email diff --git a/test/features/resilience.feature b/test/features/resilience.feature index fb180632..b252ca66 100644 --- a/test/features/resilience.feature +++ b/test/features/resilience.feature @@ -3,9 +3,7 @@ Feature: Authelia keeps user sessions despite the application restart @need-authenticated-user-john Scenario: Session is still valid after Authelia restarts When the application restarts - Then I have access to: - | url | - | https://admin.example.com:8080/secret.html | + Then I have access to "https://admin.example.com:8080/secret.html" @need-registered-user-john Scenario: Secrets are stored even when Authelia restarts diff --git a/test/features/session-timeout.feature b/test/features/session-timeout.feature index c9947e5f..1d31bb9d 100644 --- a/test/features/session-timeout.feature +++ b/test/features/session-timeout.feature @@ -3,18 +3,14 @@ Feature: Session is closed after a certain amount of time @need-authenticated-user-john Scenario: An authenticated user is disconnected after a certain inactivity period - Given I have access to: - | url | - | https://public.example.com:8080/secret.html | + Given I have access to "https://public.example.com:8080/secret.html" When I sleep for 6 seconds And I visit "https://public.example.com:8080/secret.html" Then I'm redirected to "https://login.example.com:8080/?redirect=https%3A%2F%2Fpublic.example.com%3A8080%2Fsecret.html" @need-authenticated-user-john Scenario: An authenticated user is disconnected after session expiration period - Given I have access to: - | url | - | https://public.example.com:8080/secret.html | + Given I have access to "https://public.example.com:8080/secret.html" When I sleep for 4 seconds And I visit "https://public.example.com:8080/secret.html" And I sleep for 4 seconds diff --git a/test/features/step_definitions/access-control.ts b/test/features/step_definitions/access-control.ts new file mode 100644 index 00000000..674d0edf --- /dev/null +++ b/test/features/step_definitions/access-control.ts @@ -0,0 +1,17 @@ +import {Then} from "cucumber"; + +Then("I have access to {string}", function(url: string) { + const that = this; + return this.driver.get(url) + .then(function () { + return that.waitUntilUrlContains(url); + }); +}); + +Then("I have no access to {string}", function(url: string) { + const that = this; + return this.driver.get(url) + .then(function () { + return that.getErrorPage(403); + }); +}); \ No newline at end of file diff --git a/test/features/step_definitions/authelia.ts b/test/features/step_definitions/authelia.ts index da3f9676..e08fcab7 100644 --- a/test/features/step_definitions/authelia.ts +++ b/test/features/step_definitions/authelia.ts @@ -1,51 +1,45 @@ -import Cucumber = require("cucumber"); +import {Before, When, Then} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import Request = require("request-promise"); import Bluebird = require("bluebird"); -Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) { - Before(function () { - this.jar = Request.jar(); - }) +When("I query {string}", function (url: string) { + const that = this; + return Request(url, { followRedirect: false }) + .then(function(response) { + console.log(response); + that.response = response; + }) + .catch(function(err: Error) { + that.error = err; + }) +}); - When("I query {stringInDoubleQuotes}", function (url: string) { - const that = this; - return Request(url, { followRedirect: false }) - .then(function(response) { - console.log(response); - that.response = response; - }) - .catch(function(err: Error) { - that.error = err; - }) +Then("I get error code 401", function() { + const that = this; + return new Bluebird(function(resolve, reject) { + if(that.error && that.error.statusCode == 401) { + resolve(); + } + else { + if(that.response) + reject(new Error("No error thrown")); + else if(that.error.statusCode != 401) + reject(new Error("Error code != 401")); + } }); +}); - Then("I get error code 401", function() { - const that = this; - return new Bluebird(function(resolve, reject) { - if(that.error && that.error.statusCode == 401) { - resolve(); - } - else { - if(that.response) - reject(new Error("No error thrown")); - else if(that.error.statusCode != 401) - reject(new Error("Error code != 401")); - } - }); +Then("I get redirected to {string}", function(url: string) { + const that = this; + return new Bluebird(function(resolve, reject) { + if(that.error && that.error.statusCode == 302 + && that.error.message.indexOf(url) > -1) { + resolve(); + } + else { + reject(new Error("Not redirected")); + } }); - - Then("I get redirected to {stringInDoubleQuotes}", function(url: string) { - const that = this; - return new Bluebird(function(resolve, reject) { - if(that.error && that.error.statusCode == 302 - && that.error.message.indexOf(url) > -1) { - resolve(); - } - else { - reject(new Error("Not redirected")); - } - }); - }) }); \ No newline at end of file diff --git a/test/features/step_definitions/authentication.ts b/test/features/step_definitions/authentication.ts index f7d923b8..ec30573f 100644 --- a/test/features/step_definitions/authentication.ts +++ b/test/features/step_definitions/authentication.ts @@ -1,4 +1,4 @@ -import Cucumber = require("cucumber"); +import {Given, When, Then, TableDefinition} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import Fs = require("fs"); @@ -7,127 +7,93 @@ import CustomWorld = require("../support/world"); import BluebirdPromise = require("bluebird"); import Request = require("request-promise"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) { - return this.visit(link); - }); +When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) { + return this.visit(link); +}); - When("I wait for notification to disappear", function () { - const that = this; - const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification")); - return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000) - .then(function () { - return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000); - }) - }) - - When("I set field {stringInDoubleQuotes} to {stringInDoubleQuotes}", function (fieldName: string, content: string) { - return this.setFieldTo(fieldName, content); - }); - - When("I clear field {stringInDoubleQuotes}", function (fieldName: string) { - return this.clearField(fieldName); - }); - - When("I click on {stringInDoubleQuotes}", function (text: string) { - return this.clickOnButton(text); - }); - - Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}", - function (username: string, password: string) { - return this.loginWithUserPassword(username, password); - }); - - Given("I login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes} \ -and I use TOTP token handle {stringInDoubleQuotes}", - function (username: string, password: string, totpTokenHandle: string) { - const that = this; - return this.loginWithUserPassword(username, password) - .then(function () { - return that.useTotpTokenHandle(totpTokenHandle); - }); - }); - - Given("I register a TOTP secret called {stringInDoubleQuotes}", function (handle: string) { - return this.registerTotpSecret(handle); - }); - - Given("I use {stringInDoubleQuotes} as TOTP token", function (token: string) { - return this.useTotpToken(token); - }); - - Given("I use {stringInDoubleQuotes} as TOTP token handle", function (handle) { - return this.useTotpTokenHandle(handle); - }); - - When("I visit {stringInDoubleQuotes} and get redirected {stringInDoubleQuotes}", - function (url: string, redirectUrl: string) { - const that = this; - return this.driver.get(url) - .then(function () { - return that.driver.wait(seleniumWebdriver.until.urlIs(redirectUrl), 2000); - }); - }); - - Given("I register TOTP and login with user {stringInDoubleQuotes} and password {stringInDoubleQuotes}", - function (username: string, password: string) { - return this.registerTotpAndSignin(username, password); - }); - - function hasAccessToSecret(link: string, that: any) { - return that.driver.get(link) - .then(function () { - return that.waitUntilUrlContains(link); - }); - } - - function hasNoAccessToSecret(link: string, that: any) { - return that.driver.get(link) - .then(function () { - return that.getErrorPage(403); - }); - } - - Then("I have access to:", function (dataTable: Cucumber.TableDefinition) { - const promises: any = []; - for (let i = 0; i < dataTable.rows().length; i++) { - const url = (dataTable.hashes() as any)[i].url; - promises.push(hasAccessToSecret(url, this)); - } - return BluebirdPromise.all(promises); - }); - - Then("I have no access to:", function (dataTable: Cucumber.TableDefinition) { - const promises = []; - for (let i = 0; i < dataTable.rows().length; i++) { - const url = (dataTable.hashes() as any)[i].url; - promises.push(hasNoAccessToSecret(url, this)); - } - return BluebirdPromise.all(promises); - }); - - function endpointReplyWith(context: any, link: string, method: string, - returnCode: number) { - return Request(link, { - method: method +When("I wait for notification to disappear", function () { + const that = this; + const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification")); + return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000) + .then(function () { + return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000); }) - .then(function (response: string) { - Assert(response.indexOf("Error " + returnCode) >= 0); - return BluebirdPromise.resolve(); - }, function (response: any) { - Assert.equal(response.statusCode, returnCode); - return BluebirdPromise.resolve(); - }); - } +}) - Then("the following endpoints reply with:", function (dataTable: Cucumber.TableDefinition) { - const promises = []; - for (let i = 0; i < dataTable.rows().length; i++) { - const url: string = (dataTable.hashes() as any)[i].url; - const method: string = (dataTable.hashes() as any)[i].method; - const code: number = (dataTable.hashes() as any)[i].code; - promises.push(endpointReplyWith(this, url, method, code)); - } - return BluebirdPromise.all(promises); +When("I set field {string} to {string}", function (fieldName: string, content: string) { + return this.setFieldTo(fieldName, content); +}); + +When("I clear field {string}", function (fieldName: string) { + return this.clearField(fieldName); +}); + +When("I click on {string}", function (text: string) { + return this.clickOnButton(text); +}); + +Given("I login with user {string} and password {string}", + function (username: string, password: string) { + return this.loginWithUserPassword(username, password); }); + +Given("I login with user {string} and password {string} \ +and I use TOTP token handle {string}", + function (username: string, password: string, totpTokenHandle: string) { + const that = this; + return this.loginWithUserPassword(username, password) + .then(function () { + return that.useTotpTokenHandle(totpTokenHandle); + }); + }); + +Given("I register a TOTP secret called {string}", function (handle: string) { + return this.registerTotpSecret(handle); +}); + +Given("I use {string} as TOTP token", function (token: string) { + return this.useTotpToken(token); +}); + +Given("I use {string} as TOTP token handle", function (handle) { + return this.useTotpTokenHandle(handle); +}); + +When("I visit {string} and get redirected {string}", + function (url: string, redirectUrl: string) { + const that = this; + return this.driver.get(url) + .then(function () { + return that.driver.wait(seleniumWebdriver.until.urlIs(redirectUrl), 2000); + }); + }); + +Given("I register TOTP and login with user {string} and password {string}", + function (username: string, password: string) { + return this.registerTotpAndSignin(username, password); + }); + +function endpointReplyWith(context: any, link: string, method: string, + returnCode: number) { + return Request(link, { + method: method + }) + .then(function (response: string) { + Assert(response.indexOf("Error " + returnCode) >= 0); + return BluebirdPromise.resolve(); + }, function (response: any) { + Assert.equal(response.statusCode, returnCode); + return BluebirdPromise.resolve(); + }); +} + +Then("the following endpoints reply with:", function (dataTable: TableDefinition) { + const promises = []; + for (let i = 0; i < dataTable.rows().length; i++) { + const url: string = (dataTable.hashes() as any)[i].url; + const method: string = (dataTable.hashes() as any)[i].method; + const code: number = (dataTable.hashes() as any)[i].code; + promises.push(endpointReplyWith(this, url, method, code)); + } + return BluebirdPromise.all(promises); }); \ No newline at end of file diff --git a/test/features/step_definitions/forward-headers.ts b/test/features/step_definitions/forward-headers.ts index cc53683a..cbefd1a7 100644 --- a/test/features/step_definitions/forward-headers.ts +++ b/test/features/step_definitions/forward-headers.ts @@ -1,20 +1,18 @@ -import Cucumber = require("cucumber"); +import {Then} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import CustomWorld = require("../support/world"); import Util = require("util"); import BluebirdPromise = require("bluebird"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - Then("I see header {stringInDoubleQuotes} set to {stringInDoubleQuotes}", - { timeout: 5000 }, - function (expectedHeaderName: string, expectedValue: string) { - return this.driver.findElement(seleniumWebdriver.By.tagName("body")).getText() - .then(function (txt: string) { - const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, expectedValue); - if (txt.indexOf(expectedLine) > 0) - return BluebirdPromise.resolve(); - else - return BluebirdPromise.reject(new Error(Util.format("No such header or with unexpected value."))); - }); - }) -}); \ No newline at end of file +Then("I see header {string} set to {string}", + { timeout: 5000 }, + function (expectedHeaderName: string, expectedValue: string) { + return this.driver.findElement(seleniumWebdriver.By.tagName("body")).getText() + .then(function (txt: string) { + const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, expectedValue); + if (txt.indexOf(expectedLine) > 0) + return BluebirdPromise.resolve(); + else + return BluebirdPromise.reject(new Error(Util.format("No such header or with unexpected value."))); + }); + }) diff --git a/test/features/step_definitions/hooks.ts b/test/features/step_definitions/hooks.ts index 0123a566..9e49156f 100644 --- a/test/features/step_definitions/hooks.ts +++ b/test/features/step_definitions/hooks.ts @@ -1,4 +1,4 @@ -import Cucumber = require("cucumber"); +import {setDefaultTimeout, After, Before} from "cucumber"; import fs = require("fs"); import BluebirdPromise = require("bluebird"); import ChildProcess = require("child_process"); @@ -8,133 +8,137 @@ import { MongoConnector } from "../../../server/src/lib/connectors/mongo/MongoCo import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient"; import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler"; import Speakeasy = require("speakeasy"); +import Request = require("request-promise"); -Cucumber.defineSupportCode(function ({ setDefaultTimeout }) { - setDefaultTimeout(20 * 1000); +setDefaultTimeout(20 * 1000); + +const exec = BluebirdPromise.promisify(ChildProcess.exec); + +Before(function () { + this.jar = Request.jar(); +}) + +After(function () { + return this.driver.quit(); }); -Cucumber.defineSupportCode(function ({ After, Before }) { - const exec = BluebirdPromise.promisify(ChildProcess.exec); +function createRegulationConfiguration(): BluebirdPromise { + return exec("\ + cat config.template.yml | \ + sed 's/find_time: [0-9]\\+/find_time: 15/' | \ + sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \ + "); +} - After(function () { - return this.driver.quit(); +function createInactivityConfiguration(): BluebirdPromise { + return exec("\ + cat config.template.yml | \ + sed 's/expiration: [0-9]\\+/expiration: 10000/' | \ + sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \ + "); +} + +function createSingleFactorConfiguration(): BluebirdPromise { + return exec("\ + cat config.template.yml | \ + sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \ + "); +} + +function createCustomTotpIssuerConfiguration(): BluebirdPromise { + return exec("\ + cat config.template.yml > config.test.yml && \ + echo 'totp:' >> config.test.yml && \ + echo ' issuer: custom.com' >> config.test.yml \ + "); +} + +function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise) { + Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { + return cb() + .then(function () { + return exec("./scripts/example-commit/dc-example.sh -f " + + "./example/authelia/docker-compose.test.yml up -d authelia &&" + + " sleep 1"); + }) }); - function createRegulationConfiguration(): BluebirdPromise { - return exec("\ - cat config.template.yml | \ - sed 's/find_time: [0-9]\\+/find_time: 15/' | \ - sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \ - "); - } - - function createInactivityConfiguration(): BluebirdPromise { - return exec("\ - cat config.template.yml | \ - sed 's/expiration: [0-9]\\+/expiration: 10000/' | \ - sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \ - "); - } - - function createSingleFactorConfiguration(): BluebirdPromise { - return exec("\ - cat config.template.yml | \ - sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \ - "); - } - - function createCustomTotpIssuerConfiguration(): BluebirdPromise { - return exec("\ - cat config.template.yml > config.test.yml && \ - echo 'totp:' >> config.test.yml && \ - echo ' issuer: custom.com' >> config.test.yml \ - "); - } - - function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise) { - Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { - return cb() - .then(function () { - return exec("./scripts/example-commit/dc-example.sh -f " + - "./example/authelia/docker-compose.test.yml up -d authelia &&" + - " sleep 1"); - }) - }); - - After({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { - return exec("rm config.test.yml") - .then(function () { - return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1"); - }); - }); - } - - declareNeedsConfiguration("regulation", createRegulationConfiguration); - declareNeedsConfiguration("inactivity", createInactivityConfiguration); - declareNeedsConfiguration("single_factor", createSingleFactorConfiguration); - declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration); - - function registerUser(context: any, username: string) { - let secret: Speakeasy.Key; - const mongoConnector = new MongoConnector("mongodb://localhost:27017/authelia"); - return mongoConnector.connect() - .then(function (mongoClient: IMongoClient) { - const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient); - const userDataStore = new UserDataStore(collectionFactory); - - const generator = new TotpHandler(Speakeasy); - secret = generator.generate("user", "authelia.com"); - return userDataStore.saveTOTPSecret(username, secret); - }) + After({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { + return exec("rm config.test.yml") .then(function () { - context.totpSecrets["REGISTERED"] = secret.base32; + return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1"); }); - } + }); +} - function declareNeedRegisteredUserHooks(username: string) { - Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () { - return registerUser(this, username); +declareNeedsConfiguration("regulation", createRegulationConfiguration); +declareNeedsConfiguration("inactivity", createInactivityConfiguration); +declareNeedsConfiguration("single_factor", createSingleFactorConfiguration); +declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration); + +function registerUser(context: any, username: string) { + let secret: Speakeasy.Key; + const mongoConnector = new MongoConnector("mongodb://localhost:27017"); + return mongoConnector.connect("authelia") + .then(function (mongoClient: IMongoClient) { + const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient); + const userDataStore = new UserDataStore(collectionFactory); + + const generator = new TotpHandler(Speakeasy); + secret = generator.generate("user", "authelia.com"); + return userDataStore.saveTOTPSecret(username, secret); + }) + .then(function () { + context.totpSecrets["REGISTERED"] = secret.base32; + return mongoConnector.close(); }); +} - After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () { - this.totpSecrets["REGISTERED"] = undefined; +function declareNeedRegisteredUserHooks(username: string) { + Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () { + return registerUser(this, username); + }); + + After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () { + this.totpSecrets["REGISTERED"] = undefined; + return BluebirdPromise.resolve(); + }); +} + +function needAuthenticatedUser(context: any, username: string): BluebirdPromise { + return context.visit("https://login.example.com:8080/logout") + .then(function () { + return context.visit("https://login.example.com:8080/"); + }) + .then(function () { + return registerUser(context, username); + }) + .then(function () { + return context.loginWithUserPassword(username, "password"); + }) + .then(function () { + return context.useTotpTokenHandle("REGISTERED"); + }) + .then(function () { + return context.clickOnButton("Sign in"); }); - } +} - function needAuthenticatedUser(context: any, username: string): BluebirdPromise { - return context.visit("https://login.example.com:8080/logout") - .then(function () { - return context.visit("https://login.example.com:8080/"); - }) - .then(function () { - return registerUser(context, username); - }) - .then(function () { - return context.loginWithUserPassword(username, "password"); - }) - .then(function () { - return context.useTotpTokenHandle("REGISTERED"); - }) - .then(function () { - return context.clickOnButton("Sign in"); - }); - } +function declareNeedAuthenticatedUserHooks(username: string) { + Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () { + return needAuthenticatedUser(this, username); + }); - function declareNeedAuthenticatedUserHooks(username: string) { - Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () { - return needAuthenticatedUser(this, username); - }); + After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () { + this.totpSecrets["REGISTERED"] = undefined; + return BluebirdPromise.resolve(); + }); +} - After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () { - this.totpSecrets["REGISTERED"] = undefined; - }); - } +function declareHooksForUser(username: string) { + declareNeedRegisteredUserHooks(username); + declareNeedAuthenticatedUserHooks(username); +} - function declareHooksForUser(username: string) { - declareNeedRegisteredUserHooks(username); - declareNeedAuthenticatedUserHooks(username); - } - - const users = ["harry", "john", "bob", "blackhat"]; - users.forEach(declareHooksForUser); -}); \ No newline at end of file +const users = ["harry", "john", "bob", "blackhat"]; +users.forEach(declareHooksForUser); \ No newline at end of file diff --git a/test/features/step_definitions/notifications.ts b/test/features/step_definitions/notifications.ts index 5da9d06a..4bb3ef70 100644 --- a/test/features/step_definitions/notifications.ts +++ b/test/features/step_definitions/notifications.ts @@ -1,26 +1,23 @@ -import Cucumber = require("cucumber"); +import {Then} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import Fs = require("fs"); import CustomWorld = require("../support/world"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - Then("I get a notification of type {stringInDoubleQuotes} with message {stringInDoubleQuotes}", - { timeout: 10 * 1000 }, - function (notificationType: string, notificationMessage: string) { - const that = this; - const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification")); - return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 5000) - .then(function () { - return notificationEl.getText(); - }) - .then(function (txt: string) { - Assert.equal(notificationMessage, txt); - return notificationEl.getAttribute("class"); - }) - .then(function (classes: string) { - Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element."); - return that.driver.sleep(500); - }); +Then("I get a notification of type {string} with message {string}", { timeout: 10 * 1000 }, +function (notificationType: string, notificationMessage: string) { + const that = this; + const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification")); + return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 5000) + .then(function () { + return notificationEl.getText(); + }) + .then(function (txt: string) { + Assert.equal(notificationMessage, txt); + return notificationEl.getAttribute("class"); + }) + .then(function (classes: string) { + Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element."); + return that.driver.sleep(500); }); }); \ No newline at end of file diff --git a/test/features/step_definitions/redirection.ts b/test/features/step_definitions/redirection.ts index 988bc64e..23797c5e 100644 --- a/test/features/step_definitions/redirection.ts +++ b/test/features/step_definitions/redirection.ts @@ -1,17 +1,15 @@ -import Cucumber = require("cucumber"); +import {Given, When, Then} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - Given("I'm on https://{string}", function (link: string) { - return this.driver.get("https://" + link); - }); +Given("I'm on {string}", function (link: string) { + return this.driver.get(link); +}); - When("I click on the link to {string}", function (link: string) { - return this.driver.findElement(seleniumWebdriver.By.linkText(link)).click(); - }); +When("I click on the link to {string}", function (link: string) { + return this.driver.findElement(seleniumWebdriver.By.linkText(link)).click(); +}); - Then("I'm redirected to {stringInDoubleQuotes}", function (link: string) { - return this.waitUntilUrlContains(link); - }); +Then("I'm redirected to {string}", function (link: string) { + return this.waitUntilUrlContains(link); }); \ No newline at end of file diff --git a/test/features/step_definitions/registration.ts b/test/features/step_definitions/registration.ts index beab5c5b..7e861c54 100644 --- a/test/features/step_definitions/registration.ts +++ b/test/features/step_definitions/registration.ts @@ -1,15 +1,13 @@ -import Cucumber = require("cucumber"); +import {When} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When("the otpauth url has label {stringInDoubleQuotes} and issuer \ -{stringInDoubleQuotes}", function (label: string, issuer: string) { - return this.driver.findElement(seleniumWebdriver.By.id("qrcode")) - .getAttribute("title") - .then(function (title: string) { - const re = `^otpauth://totp/${label}\\?secret=[A-Z0-9]+&issuer=${issuer}$`; - Assert(new RegExp(re).test(title)); - }) - }); -}); +When("the otpauth url has label {string} and issuer \ +{string}", function (label: string, issuer: string) { + return this.driver.findElement(seleniumWebdriver.By.id("qrcode")) + .getAttribute("title") + .then(function (title: string) { + const re = `^otpauth://totp/${label}\\?secret=[A-Z0-9]+&issuer=${issuer}$`; + Assert(new RegExp(re).test(title)); + }) + }); diff --git a/test/features/step_definitions/regulation.ts b/test/features/step_definitions/regulation.ts index 12766d49..c2ac4628 100644 --- a/test/features/step_definitions/regulation.ts +++ b/test/features/step_definitions/regulation.ts @@ -1,11 +1,9 @@ -import Cucumber = require("cucumber"); +import {When} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import Fs = require("fs"); import CustomWorld = require("../support/world"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When("I wait {number} seconds", { timeout: 10 * 1000 }, function (seconds: number) { - return this.driver.sleep(seconds * 1000); - }); -}); \ No newline at end of file +When("I wait {int} seconds", { timeout: 10 * 1000 }, function (seconds: number) { + return this.driver.sleep(seconds * 1000); +}); diff --git a/test/features/step_definitions/reset-password.ts b/test/features/step_definitions/reset-password.ts index c27f751a..d84c4a30 100644 --- a/test/features/step_definitions/reset-password.ts +++ b/test/features/step_definitions/reset-password.ts @@ -1,18 +1,16 @@ -import Cucumber = require("cucumber"); +import {When} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import Fs = require("fs"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When("I click on the link {stringInDoubleQuotes}", function (text: string) { - return this.driver.findElement(seleniumWebdriver.By.linkText(text)).click(); - }); +When("I click on the link {string}", function (text: string) { + return this.driver.findElement(seleniumWebdriver.By.linkText(text)).click(); +}); - When("I click on the link of the email", function () { - const that = this; - return this.retrieveLatestMail() - .then(function (link: string) { - return that.driver.get(link); - }); - }); +When("I click on the link of the email", function () { + const that = this; + return this.retrieveLatestMail() + .then(function (link: string) { + return that.driver.get(link); + }); }); \ No newline at end of file diff --git a/test/features/step_definitions/resilience.ts b/test/features/step_definitions/resilience.ts index 50930841..2032d78a 100644 --- a/test/features/step_definitions/resilience.ts +++ b/test/features/step_definitions/resilience.ts @@ -1,12 +1,10 @@ -import Cucumber = require("cucumber"); +import {When} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import ChildProcess = require("child_process"); import BluebirdPromise = require("bluebird"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When(/^the application restarts$/, {timeout: 15 * 1000}, function () { - const exec = BluebirdPromise.promisify(ChildProcess.exec); - return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 1"); - }); +When(/^the application restarts$/, {timeout: 15 * 1000}, function () { + const exec = BluebirdPromise.promisify(ChildProcess.exec); + return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 1"); }); \ No newline at end of file diff --git a/test/features/step_definitions/restrictions.ts b/test/features/step_definitions/restrictions.ts index 028113bb..cf7eb0c1 100644 --- a/test/features/step_definitions/restrictions.ts +++ b/test/features/step_definitions/restrictions.ts @@ -1,72 +1,69 @@ -import Cucumber = require("cucumber"); +import {Before, When, Then, TableDefinition} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Assert = require("assert"); import Request = require("request-promise"); import Bluebird = require("bluebird"); -Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) { - Before(function () { - this.jar = Request.jar(); - }) +Before(function () { + this.jar = Request.jar(); +}); - Then("I get an error {number}", function (code: number) { - return this.getErrorPage(code); +Then("I get an error {int}", function (code: number) { + return this.getErrorPage(code); +}); + +When("I request {string} with method {string}", + function (url: string, method: string) { + const that = this; }); - When("I request {stringInDoubleQuotes} with method {stringInDoubleQuotes}", - function (url: string, method: string) { - const that = this; - - }) - - function requestAndExpectStatusCode(ctx: any, url: string, method: string, - expectedStatusCode: number) { - return Request(url, { - method: method, - jar: ctx.jar - }) - .then(function (body: string) { - return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1])); - }, function (response: any) { - return Bluebird.resolve(response.statusCode) - }) - .then(function (statusCode: number) { - try { - Assert.equal(statusCode, expectedStatusCode); - } - catch (e) { - console.log(url); - console.log("%s (actual) != %s (expected)", statusCode, - expectedStatusCode); - throw e; - } - }) - } - - Then("I get the following status code when requesting:", - function (dataTable: Cucumber.TableDefinition) { - const promises: Bluebird[] = []; - for (let i = 0; i < dataTable.rows().length; i++) { - const url: string = (dataTable.hashes() as any)[i].url; - const method: string = (dataTable.hashes() as any)[i].method; - const code: number = (dataTable.hashes() as any)[i].code; - promises.push(requestAndExpectStatusCode(this, url, method, code)); - } - return Bluebird.all(promises); - }) - - When("I post {stringInDoubleQuotes} with body:", function (url: string, - dataTable: Cucumber.TableDefinition) { - const body = {}; - for (let i = 0; i < dataTable.rows().length; i++) { - const key = (dataTable.hashes() as any)[i].key; - const value = (dataTable.hashes() as any)[i].value; - body[key] = value; - } - return Request.post(url, { - body: body, - jar: this.jar, - json: true - }); +function requestAndExpectStatusCode(ctx: any, url: string, method: string, + expectedStatusCode: number) { + return Request(url, { + method: method, + jar: ctx.jar }) + .then(function (body: string) { + return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1])); + }, function (response: any) { + return Bluebird.resolve(response.statusCode) + }) + .then(function (statusCode: number) { + try { + Assert.equal(statusCode, expectedStatusCode); + } + catch (e) { + console.log(url); + console.log("%s (actual) != %s (expected)", statusCode, + expectedStatusCode); + throw e; + } + }) +} + +Then("I get the following status code when requesting:", + function (dataTable: TableDefinition) { + const promises: Bluebird[] = []; + for (let i = 0; i < dataTable.rows().length; i++) { + const url: string = (dataTable.hashes() as any)[i].url; + const method: string = (dataTable.hashes() as any)[i].method; + const code: number = (dataTable.hashes() as any)[i].code; + promises.push(requestAndExpectStatusCode(this, url, method, code)); + } + return Bluebird.all(promises); + }) + +When("I post {string} with body:", function (url: string, + dataTable: TableDefinition) { + const body = {}; + for (let i = 0; i < dataTable.rows().length; i++) { + const key = (dataTable.hashes() as any)[i].key; + const value = (dataTable.hashes() as any)[i].value; + body[key] = value; + } + return Request.post(url, { + body: body, + jar: this.jar, + json: true + }); }); \ No newline at end of file diff --git a/test/features/step_definitions/session-timeout.ts b/test/features/step_definitions/session-timeout.ts index 98131274..bbeb66f7 100644 --- a/test/features/step_definitions/session-timeout.ts +++ b/test/features/step_definitions/session-timeout.ts @@ -1,8 +1,6 @@ -import Cucumber = require("cucumber"); +import {When} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When("I sleep for {number} seconds", function (seconds: number) { - return this.driver.sleep(seconds * 1000); - }); -}); \ No newline at end of file +When("I sleep for {int} seconds", function (seconds: number) { + return this.driver.sleep(seconds * 1000); +}); diff --git a/test/features/step_definitions/single-factor.ts b/test/features/step_definitions/single-factor.ts index cde0dd90..222480c7 100644 --- a/test/features/step_definitions/single-factor.ts +++ b/test/features/step_definitions/single-factor.ts @@ -1,39 +1,37 @@ -import Cucumber = require("cucumber"); +import {When, Then} from "cucumber"; import seleniumWebdriver = require("selenium-webdriver"); import Request = require("request-promise"); import BluebirdPromise = require("bluebird"); import Util = require("util"); -Cucumber.defineSupportCode(function ({ Given, When, Then }) { - When("I request {stringInDoubleQuotes} with username {stringInDoubleQuotes}" + - " and password {stringInDoubleQuotes} using basic authentication", - function (url: string, username: string, password: string) { - const that = this; - return Request(url, { - auth: { - username: username, - password: password - }, - resolveWithFullResponse: true - }) - .then(function (response: any) { - that.response = response; - }); - }); - - Then("I receive the secret page", function () { - if (this.response.body.match("This is a very important secret!")) - return BluebirdPromise.resolve(); - return BluebirdPromise.reject(new Error("Secret page not received.")); +When("I request {string} with username {string}" + + " and password {string} using basic authentication", + function (url: string, username: string, password: string) { + const that = this; + return Request(url, { + auth: { + username: username, + password: password + }, + resolveWithFullResponse: true + }) + .then(function (response: any) { + that.response = response; + }); }); - Then("I received header {stringInDoubleQuotes} set to {stringInDoubleQuotes}", - function (expectedHeaderName: string, expectedValue: string) { - const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, - expectedValue); - if (this.response.body.indexOf(expectedLine) > 0) - return BluebirdPromise.resolve(); - return BluebirdPromise.reject(new Error( - Util.format("No such header or with unexpected value."))); - }) -}); \ No newline at end of file +Then("I receive the secret page", function () { + if (this.response.body.match("This is a very important secret!")) + return BluebirdPromise.resolve(); + return BluebirdPromise.reject(new Error("Secret page not received.")); +}); + +Then("I received header {string} set to {string}", + function (expectedHeaderName: string, expectedValue: string) { + const expectedLine = Util.format("\"%s\": \"%s\"", expectedHeaderName, + expectedValue); + if (this.response.body.indexOf(expectedLine) > 0) + return BluebirdPromise.resolve(); + return BluebirdPromise.reject(new Error( + Util.format("No such header or with unexpected value."))); + }); diff --git a/test/features/support/world.ts b/test/features/support/world.ts index 32f3ce39..c1127dc7 100644 --- a/test/features/support/world.ts +++ b/test/features/support/world.ts @@ -1,6 +1,6 @@ require("chromedriver"); import seleniumWebdriver = require("selenium-webdriver"); -import Cucumber = require("cucumber"); +import {setWorldConstructor, After} from "cucumber"; import Fs = require("fs"); import Speakeasy = require("speakeasy"); import Assert = require("assert"); @@ -63,7 +63,7 @@ function CustomWorld() { this.waitUntilUrlContains = function (url: string) { const that = this; return this.driver.wait(seleniumWebdriver.until.urlIs(url), 15000) - .then(function () { }, function (err: Error) { + .then(function () {return BluebirdPromise.resolve(); }, function (err: Error) { that.driver.getCurrentUrl() .then(function (current: string) { console.error("====> Error due to: %s (current) != %s (expected)", current, url); @@ -176,6 +176,4 @@ function CustomWorld() { }; } -Cucumber.defineSupportCode(function ({ setWorldConstructor }) { - setWorldConstructor(CustomWorld); -}); +setWorldConstructor(CustomWorld);