├── sense-go.yml
├── test
├── fixtures
│ ├── error.txt
│ ├── extensions
│ │ └── qrs-sample
│ │ │ ├── javascript.js
│ │ │ ├── sub
│ │ │ ├── javascript.js
│ │ │ ├── json.json
│ │ │ └── html.html
│ │ │ ├── json.json
│ │ │ ├── html.html
│ │ │ └── qrs-sample.qext
│ ├── foobar.txt
│ └── foobarbaz.txt
├── unit
│ ├── 04-ep-mime.spec.js
│ ├── 00-validator.spec.js
│ ├── 03-ep-extension.spec.js
│ ├── 02-qrs.spec.js
│ └── 01-setup.spec.js
├── test-config.yml
├── e2e
│ ├── 02-qrs.e2e.spec.js
│ ├── 03-ep-extension.e2e.spec.js
│ └── 04-ep-mime.e2e.spec.js
├── 03-ep-extension.setup.js
└── testSetup.js
├── docs
├── images
│ ├── qrs.png
│ └── qrs-440x220.png
├── test-basic.md
├── contributing.md
├── author.md
├── examples.md
├── tests.md
├── usage.md
├── server-setup.md
└── config-options.md
├── lib
├── config-validator.js
├── logger.js
├── sugar
│ ├── ep-extension.js.bak
│ ├── ep-extension.js
│ └── ep-mime.js
└── qrs.js
├── .gitattributes
├── .travis.yml
├── index.js
├── .gitignore
├── .jshintrc
├── .editorconfig
├── CHANGELOG.yml
├── LICENSE.md
├── gulpfile.js
├── .verb.md
├── package.json
└── README.md
/sense-go.yml:
--------------------------------------------------------------------------------
1 | packageName: "qrs"
2 |
--------------------------------------------------------------------------------
/test/fixtures/error.txt:
--------------------------------------------------------------------------------
1 | foo;;;false
2 | ;text/foobar;;false
3 |
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/javascript.js:
--------------------------------------------------------------------------------
1 | // This is JavaScript
2 |
--------------------------------------------------------------------------------
/test/fixtures/foobar.txt:
--------------------------------------------------------------------------------
1 | foo;text/foobar;;false
2 | bar;text/foobar;;false
3 |
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/sub/javascript.js:
--------------------------------------------------------------------------------
1 | // This is JavaScript
2 |
--------------------------------------------------------------------------------
/docs/images/qrs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanwalther/qrs/HEAD/docs/images/qrs.png
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/json.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "I am a json file"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/sub/json.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "I am a json file"
3 | }
4 |
--------------------------------------------------------------------------------
/docs/test-basic.md:
--------------------------------------------------------------------------------
1 | Install dev dependencies:
2 |
3 | ```
4 | $ npm i -d && npm test
5 | ```
6 |
--------------------------------------------------------------------------------
/docs/images/qrs-440x220.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanwalther/qrs/HEAD/docs/images/qrs-440x220.png
--------------------------------------------------------------------------------
/test/fixtures/foobarbaz.txt:
--------------------------------------------------------------------------------
1 | foo;text/foobar;;false
2 | bar;text/foobar;;false
3 | baz;text/baz;;false
4 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | Pull requests and stars are always welcome. For bugs and feature requests, please create an issue.
2 |
--------------------------------------------------------------------------------
/lib/config-validator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require( 'lodash' );
3 |
4 | var ConfigValidator = function () {
5 |
6 |
7 |
8 | };
9 | module.exports = new ConfigValidator();
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | * text eol=lf
3 |
4 | # binaries
5 | *.ai binary
6 | *.psd binary
7 | *.jpg binary
8 | *.gif binary
9 | *.png binary
10 | *.jpeg binary
11 |
--------------------------------------------------------------------------------
/docs/author.md:
--------------------------------------------------------------------------------
1 | **Stefan Walther**
2 | * [qliksite.io](http://qliksite.io)
3 | * [twitter/waltherstefan](http://twitter.com/waltherstefan)
4 | * [github.com/stefanwalther](http://github.com/stefanwalther)
5 |
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/html.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTML file
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/sub/html.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTML file
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "8.9.4"
5 | git:
6 | depth: 10
7 | install: npm install
8 | env:
9 | - TEST_DIR=test/unit
10 | script: cd $TEST_DIR && npm install && npm test
11 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * qrs
3 | *
4 | * Copyright (c) 2015, Stefan Walther.
5 | * Licensed under the MIT License.
6 | */
7 |
8 | 'use strict';
9 | module.exports = require( './lib/qrs.js' );
10 |
--------------------------------------------------------------------------------
/test/fixtures/extensions/qrs-sample/qrs-sample.qext:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "qrs-sample",
3 | "description" : "",
4 | "icon" : "extension",
5 | "type" : "visualization",
6 | "version": "0.1",
7 | "preview" : "bar",
8 | "author": "Stefan Walther"
9 | }
10 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | ### Using the generic `query`
2 | (TBD)
3 |
4 | ### Using `get`
5 | (TBD)
6 |
7 | ### Using `delete`
8 | (TBD)
9 |
10 | ### Using `put`
11 | (TBD)
12 |
13 | ### Retrieving all endpoints
14 | (TBD)
15 |
16 | ### Get a list of all extensions
17 | (TBD)
18 |
19 | ### Upload an extension
20 | (TBD)
21 |
--------------------------------------------------------------------------------
/docs/tests.md:
--------------------------------------------------------------------------------
1 | Install dev dependencies first:
2 |
3 | `npm i -d`
4 |
5 | To be able to run the tests you have to set up a Qlik Sense server first.
6 |
7 | (TBD)
8 |
9 | ### Configuring your tests
10 | (TBD)
11 |
12 | ### Run the tests
13 |
14 | `npm test`
15 |
16 | This will run the [Mocha](https://mochajs.org/) based tests.
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | coverage/
4 | *.DS_Store
5 | *.sublime-*
6 | _gh_pages
7 | bower_components/
8 | node_modules/
9 | npm-debug.log
10 | actual/
11 | test/actual
12 | temp/
13 | tmp/
14 | .tmp/
15 | todos.md
16 | vendor
17 | scratches/
18 | /test/mimetypes.txt
19 | /test/mimetypes_single.json
20 | /test/fixtures/extensions/*.zip
21 | /test/fixtures/extensions/*.7z
22 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 |
2 | ```js
3 | var QRS = require('{%= name %}');
4 | var config = {
5 | "host": 'qsSingle',
6 | "useSSL": false,
7 | "xrfkey": 'ABCDEFG123456789',
8 | "authentication": "header",
9 | "headerKey": 'hdr-usr',
10 | "headerValue": 'qsSingle\\swr'
11 | };
12 | var qrs = new QRS( config );
13 |
14 | // Now run your command like
15 | qrs.get('qrs/about', function( data ) {
16 |
17 | // do something with the result
18 |
19 | });
20 | ```
21 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "eqeqeq": true,
8 | "immed": true,
9 | "indent": 2,
10 | "latedef": true,
11 | "newcap": true,
12 | "noarg": true,
13 | "quotmark": "single",
14 | "regexp": true,
15 | "undef": true,
16 | "unused": true,
17 | "strict": true,
18 | "trailing": true,
19 | "smarttabs": true,
20 | "white": true,
21 | "browser": true,
22 | "jquery": true,
23 | "devel": true
24 | }
25 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # 4 tab indentation
12 | [*.*]
13 | indent_style = tab
14 | indent_size = 4
15 |
16 | # 2 space indentation for package.json since this is npm default
17 | [package.json]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | [*.yml]
22 | indent_style = space
23 | indent_size = 2
24 |
--------------------------------------------------------------------------------
/test/unit/04-ep-mime.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,it,beforeEach,before*/
2 | /*jshint -W030,-W117*/
3 | 'use strict';
4 |
5 | var chai = require( 'chai' );
6 | var expect = chai.expect;
7 | var QRS = new require( './../../lib/qrs' );
8 | var testSetup = require( './../testSetup' );
9 |
10 | var qrs;
11 | var globalConfig = testSetup.globalConfig;
12 |
13 | describe( 'Unit: ep-mime', function () {
14 |
15 | beforeEach( function ( done ) {
16 | qrs = new QRS( globalConfig );
17 | done();
18 | } );
19 |
20 | it( 'should be an object', function () {
21 | expect( qrs.mime ).to.exist;
22 | } );
23 |
24 | it( 'should contain methods', function ( done ) {
25 | expect( qrs.mime.get ).to.exist;
26 | done();
27 | } );
28 | } );
29 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var winston = require( 'winston' );
3 |
4 | var logConfig = {
5 | levels: {
6 | silly: 0,
7 | verbose: 1,
8 | info: 2,
9 | data: 3,
10 | warn: 4,
11 | debug: 5,
12 | error: 6
13 | },
14 | colors: {
15 | silly: 'magenta',
16 | verbose: 'cyan',
17 | info: 'green',
18 | data: 'grey',
19 | warn: 'yellow',
20 | debug: 'blue',
21 | error: 'red'
22 | }
23 | };
24 |
25 | var logger = new (winston.Logger)( {
26 | transports: [
27 | new (winston.transports.Console)( {
28 | colorize: true,
29 | level: 'silly',
30 | json: false,
31 | timestamp: false,
32 | handleExceptions: false
33 | } )
34 | ],
35 | levels: logConfig.levels,
36 | colors: logConfig.colors
37 | } );
38 |
39 | module.exports = logger;
40 |
--------------------------------------------------------------------------------
/test/unit/00-validator.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,beforeEach,it*/
2 | /*jshint -W030,-W117*/
3 | 'use strict';
4 | var chai = require( 'chai' );
5 | var expect = chai.expect;
6 | var validator = require( './../../lib/config-validator' );
7 |
8 | describe( 'Validator', function () {
9 |
10 | //it( 'should be an object', function ( ) {
11 | // expect( validator ).to.be.an.object;
12 | //} );
13 | //it( 'should expose .validators', function ( ) {
14 | // expect( validator ).to.have.property( 'validators');
15 | //} );
16 | //
17 | //it( 'has no validators by default', function ( ) {
18 | // expect( validator.validators ).to.have.length(0);
19 | //} );
20 | //it( 'should accept new validators', function ( done ) {
21 | //
22 | // validator.use( 'test', function ( ) {
23 | // return 'testValue';
24 | // });
25 | //
26 | //} );
27 |
28 | } );
--------------------------------------------------------------------------------
/docs/server-setup.md:
--------------------------------------------------------------------------------
1 | There are several options to ensure that communication between this node.js module and Qlik Sense server is working properly:
2 |
3 | **Authenticating with HTTP headers**
4 | * [Using Header Authentication in Qlik Sense: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/header-authentication-configuration)
5 | * [Authenticating with HTTP headers](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Http-Headers.htm) in Qlik Sense Help for Developers 2.1.1
6 |
7 |
8 | **Authenticating with a server certificate**
9 | * [Authenticating with Certificates: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/authentication-certificates)
10 | * [Authenticating with the server certificate](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Certificate.htm) in Qlik Sense Help for Developer 2.1.1
11 |
--------------------------------------------------------------------------------
/test/unit/03-ep-extension.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,beforeEach,it*/
2 | /*jshint -W030,-W117*/
3 |
4 | 'use strict';
5 |
6 | var chai = require( 'chai' );
7 | var chaiAsPromised = require( 'chai-as-promised' );
8 | var expect = chai.expect;
9 | var QRS = new require( './../../lib/qrs' );
10 | var nock = require( 'nock' );
11 | var testSetup = require( './../testSetup' );
12 | var extend = require('extend-shallow');
13 |
14 | chai.use( chaiAsPromised );
15 |
16 | describe( 'Unit: ep-extension', function () {
17 |
18 | var qrs = null;
19 | beforeEach( function () {
20 | var config = extend( testSetup.globalConfig, {
21 | 'authentication': 'header'
22 | });
23 | qrs = new QRS( config );
24 | } );
25 |
26 | it.skip( 'should return only extensions of type ', function ( done ) {
27 | qrs.extension.getInstalledVis()
28 | .then( function ( data ) {
29 | expect( data ).to.exist;
30 | expect( data ).to.be.an.array;
31 | }, function ( err ) {
32 | expect( err ).to.not.exist;
33 | } )
34 | .done( function () {
35 | done();
36 | } );
37 | } );
38 |
39 | } );
--------------------------------------------------------------------------------
/CHANGELOG.yml:
--------------------------------------------------------------------------------
1 | v2.0.3:
2 | date: "2018-01-21"
3 | enhancements:
4 | - "Update dependencies"
5 | - "Remove codeship.io"
6 | v2.0.2:
7 | date: "2015-11-24"
8 | enhacements:
9 | - "Fix several smaller issues in qrs-mime"
10 | - "Add gulp-mocha + gulp-istanbul"
11 | v2.0.1:
12 | date: "2015-11-24"
13 | enhancements:
14 | - "Update all dependencies"
15 | v2.0.0:
16 | date: "2015-11-23"
17 | changes:
18 | - "Breaking Change: Every endpoint will not be automatically prefixed with 'qrs' anymore, so instead of `qrs.get('about', ...)` use `qrs.get('qrs/about' ...)` now."
19 | v1.2.1:
20 | date: "2015-11-20"
21 | enhancements:
22 | - "qrs.extension.upload"
23 | - "qrs.extension.delete"
24 | v1.1.1:
25 | date: "2015-11-20"
26 | enhancements:
27 | - "Bettor error handling for qrs.getUrl, especially urlParams"
28 | v1.1.0:
29 | date: "2015-11-03"
30 | enhancements:
31 | - "Finish documentation."
32 | v1.0.0:
33 | date: "2015-10-13"
34 | changes:
35 | - "Breaking change: Changed qrs.query to qrs.request"
36 | fixes:
37 | - "Merge PR: Adding missing require and typechecking certs (#1)"
38 |
--------------------------------------------------------------------------------
/test/test-config.yml:
--------------------------------------------------------------------------------
1 | host: qssingle
2 | port: 0 ## Leave the port blank or set it to 0 if you don't want to specify a port
3 | useSSL: true
4 | xrfkey: ABCDEFG123456789
5 | fiddler: false
6 | authentication:
7 | header:
8 | enabled: true
9 | useSSL: false
10 | virtualProxy: hdr
11 | headerKey: hdr-usr
12 | headerValue: QSSINGLE\swr ## Note: use only a single backslash hare, not Javascript encoding is necessary in YML
13 | ntlm:
14 | enabled: false
15 | virtualProxy:
16 | certificates:
17 | enabled: true
18 | virtualProxy:
19 | cert: D:\CertStore\qrs-test\client.pem # Do not escape backslash in yml !
20 | key: D:\CertStore\qrs-test\client_key.pem # Do not escape backslash in yml !
21 | ca: D:\CertStore\qrs-test\root.pem # Do not escape backslash in yml !
22 | passphrase:
23 | port: 4242
24 | useSSL: true
25 | headerKey: X-Qlik-User
26 | headerValue: UserDirectory=Internal;UserId=sa_repository
27 | windows:
28 | enabled: false
29 | virtualProxy:
30 | useSSL: true
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2018, Stefan Walther.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/e2e/02-qrs.e2e.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe, it, beforeEach*/
2 | /*jshint -W030,-W117*/
3 |
4 | 'use strict';
5 | var chai = require( 'chai' );
6 | var expect = chai.expect;
7 | var assert = chai.assert;
8 | var QRS = new require( './../../lib/qrs' );
9 | var extend = require( 'extend-shallow' );
10 | var testSetup = require( './../testSetup' );
11 | var chaiAsPromised = require('chai-as-promised');
12 |
13 | chai.use(chaiAsPromised);
14 |
15 | var qrs;
16 | var globalConfig = testSetup.globalConfig;
17 |
18 | describe( 'qrs', function () {
19 |
20 | var testConfig = null;
21 | testSetup.testLoop.forEach( function ( testLoopConfig ) {
22 |
23 | describe( 'with ' + testLoopConfig.name, function () {
24 |
25 | /**
26 | * Reset the configuration before each test.
27 | */
28 | beforeEach( function ( done ) {
29 |
30 | testConfig = extend( globalConfig, testLoopConfig.config );
31 | qrs = new QRS( testConfig );
32 | done();
33 |
34 | } );
35 |
36 | it( 'should return something for /about', function ( ) {
37 |
38 | return expect( qrs.get('/qrs/about') ).to.eventually.have.property('schemaPath', 'About');
39 |
40 | } );
41 |
42 | } );
43 | } );
44 | } );
45 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var gulp = require( 'gulp' );
3 | var senseGo = require( 'sense-go' );
4 | var path = require( 'path' );
5 | var gulpVerb = require( 'gulp-verb' );
6 | var template = require( 'template' )();
7 | var mocha = require( 'gulp-mocha' );
8 | var istanbul = require( 'gulp-istanbul' );
9 |
10 | template.helper( 'apidocs', require( 'template-helper-apidocs' ) );
11 |
12 | var userConfig = senseGo.loadYml( path.join( __dirname, 'sense-go.yml' ) );
13 | senseGo.init( gulp, userConfig, function () {
14 | gulp.task( 'all', gulp.series(
15 | 'bump:patch',
16 | 'git:add',
17 | 'git:commit',
18 | 'git:push',
19 | 'npm:publish'
20 | ) );
21 |
22 | gulp.task( 'verb', function () {
23 | gulp.src( './.verb.md' )
24 | .pipe( gulpVerb( {dest: './README.md'} ) )
25 | .pipe( gulp.dest( './' ) );
26 | } );
27 |
28 | gulp.task( 'test:unit', function ( ) {
29 | return gulp.src(['./test/unit/**/*.spec.js'])
30 | .pipe( mocha( {reporter: 'spec'} ) );
31 | });
32 |
33 | gulp.task( 'istanbul:pre-test', function () {
34 | return gulp.src( ['./lib/**/*.js'] )
35 | .pipe( istanbul() )
36 | .pipe( istanbul.hookRequire() );
37 | } );
38 | gulp.task( 'istanbul:unit', function () {
39 | return gulp.src( ['./test/unit/**/*.spec.js'] )
40 | .pipe( mocha( ) )
41 | .pipe( istanbul.writeReports() );
42 | //.pipe( istanbul.enforceThresholds( {thresholds: {global: 90}} ) );
43 | } );
44 | gulp.task( 'coverage:unit', gulp.series( 'istanbul:pre-test', 'istanbul:unit' ) );
45 | } );
46 |
--------------------------------------------------------------------------------
/docs/config-options.md:
--------------------------------------------------------------------------------
1 | The configuration passed to the constructor of *qrs* drives how authentication is handled.
2 |
3 | ### Typical configurations
4 |
5 | **Example using header authentication**
6 |
7 | ```javascript
8 | var config = {
9 | authentication: 'header',
10 | host: 'server.mydomain.com',
11 | useSSL: true,
12 | virtualProxy: 'hdr',
13 | headerKey: 'hdr-usr',
14 | headerValue: 'mydomain\\justme'
15 | };
16 | ```
17 |
18 | **Example using certificates**
19 |
20 | ```js
21 | var config = {
22 | authentication: 'certificates',
23 | host: 'server.mydomain.com',
24 | useSSL: true,
25 | cert: 'C:\\CertStore\\client.pem',
26 | key: 'C:\\CertStore\\client_key.pem',
27 | ca: 'C:\\CertStore\\root.pem',
28 | port: 4242,
29 | headerKey: 'X-Qlik-User',
30 | headerValue: 'UserDirectory=Internal;UserId=sa_repository'
31 | };
32 | ```
33 |
34 | ### All options
35 |
36 | * **`authentication`** - Authentication method, can be "`windows`", "`certificates`" or "`header`", defaults to "`windows`".
37 | * **`host`** - Qualified / fully qualified name or IP-address of the server where the Qlik Sense Repository server is running on, defaults to "`127.0.0.1`"
38 | * **`useSSL`** - Whether to use SSL or not, defaults to `false`.
39 | * **`headerKey`** - Header key.
40 | * **`headerValue`** - Header value.
41 | * **`virtualProxy`** - Name of the virtual proxy.
42 | * **`port`** - Port to be used.
43 | * **`cert`** - Path to client certificate file (client.pem).
44 | * **`key`** - Path to client key file (client_key.pem).
45 | * **`ca`** - Path to ca file (root.pem)
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.verb.md:
--------------------------------------------------------------------------------
1 | {%= badge("fury") %} {%= badge("travis") %} [](https://david-dm.org/stefanwalther/qrs)
2 | # {%= name %}
3 | > {%= description %}
4 |
5 | NOTE: This solution is not actively maintained anymore. Contributors to take over are highly welcome.
6 | Alternatively use [qrs-interact](https://github.com/eapowertools/qrs-interact)
7 |
8 | 
9 |
10 | ## Installation
11 | {%= include("install-npm", {save: true}) %}
12 |
13 | ---
14 | ## Table of Contents
15 |
16 |
17 | ---
18 |
19 | ## Usage
20 | {%= docs('usage') %}
21 |
22 | ## Configuration Options
23 | {%= docs('config-options') %}
24 |
25 | ## Server Setup
26 | {%= docs('server-setup') %}
27 |
28 | ## API
29 | {%= apidocs("lib/qrs.js") %}
30 |
31 | ## Plugins
32 | Think of plugins as some kind of sugar layer with additional business logic on top of `qrs`.
33 | It is easy to either add new plugins to the `qrs` repository or just to load some external code/plugin.
34 | The list of built-in plugins is probably and hopefully a growing one (and hopefully not only created by the author of this document).
35 |
36 | The following plugins are available with the current version of `qrs`:
37 |
38 |
39 |
40 |
41 |
42 | ### Plugin "Mime"
43 | {%= apidocs("lib/sugar/ep-mime.js") %}
44 | ---
45 |
46 | ## Running tests
47 | {%= include("tests") %}
48 |
49 | ## Contributing
50 | {%= include("contributing") %}
51 |
52 | ## Author
53 | {%= docs("author") %}
54 |
55 | ## License
56 | {%= copyright() %}
57 | {%= license %}
58 |
59 | ***
60 | {%= include("footer") %}
61 |
--------------------------------------------------------------------------------
/test/03-ep-extension.setup.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var zipper = require( 'zip-local' );
4 | var fs = require( 'fs' );
5 | var path = require( 'path' );
6 | var rimraf = require( 'rimraf' );
7 | var async = require( 'async' );
8 |
9 | var ExtensionSetup = function () {
10 |
11 | var extensions = [];
12 |
13 | var init = function ( callback ) {
14 |
15 | var dirToScan = path.join( __dirname, './fixtures/extensions' );
16 | fs.readdir( dirToScan, function ( err, files ) {
17 |
18 | var extensionDirs = files.map( function ( file ) {
19 | return path.join( dirToScan, file );
20 | } ).filter( function ( file ) {
21 | return fs.statSync( file ).isDirectory();
22 | } );
23 |
24 | async.map( extensionDirs, function ( file, cb ) {
25 | var zipSource = path.normalize( file );
26 | var fi = path.parse( file );
27 | var zipDest = path.normalize( path.join( fi.dir, fi.base + '.zip' ));
28 | rimraf( zipDest, function ( /* err */ ) {
29 | zipper.sync.zip( zipSource ).compress().save( zipDest );
30 | var ext = {
31 | 'name': fi.base,
32 | 'file': zipDest
33 | };
34 | cb( null, ext );
35 | } );
36 | }, function ( err, results ) {
37 | extensions = results;
38 | callback( err, results );
39 | } );
40 | } );
41 |
42 | };
43 |
44 | var cleanExtZipFiles = function ( callback ) {
45 |
46 | async.map( extensions, function ( ext, cb ) {
47 | rimraf( ext.file, function ( err ) {
48 | cb( null, ext);
49 | });
50 | }, function ( err, results ) {
51 | callback( null, results);
52 | });
53 |
54 | };
55 |
56 | return {
57 | init: init,
58 | extensions: extensions,
59 | cleanExtZipFiles: cleanExtZipFiles
60 | }
61 |
62 | };
63 | module.exports = ExtensionSetup;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qrs",
3 | "version": "2.0.3",
4 | "description": "Node.js library to communicate with Qlik Sense Repository Service (QRS) API.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha test/unit",
8 | "test:unit": "mocha test/unit",
9 | "coverage:unit": "gulp coverage:unit",
10 | "test:e2e": "mocha test/e2e",
11 | "preinstall": "npm install istanbul -g",
12 | "docs": "docker run --rm -v ${PWD}:/opt/verb stefanwalther/verb"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/stefanwalther/qrs.git"
17 | },
18 | "keywords": [
19 | "qlik",
20 | "sense",
21 | "qrs",
22 | "qlik-sense-repository",
23 | "api",
24 | "rest",
25 | "automation"
26 | ],
27 | "author": "Stefan Walther",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/stefanwalther/qrs/issues"
31 | },
32 | "homepage": "https://github.com/stefanwalther/qrs",
33 | "dependencies": {
34 | "async": "^2.6.0",
35 | "brace-expansion": "^1.1.8",
36 | "camelcase": "^4.1.0",
37 | "extend-shallow": "^3.0.2",
38 | "fs-utils": "^0.7.0",
39 | "glob": "^7.1.2",
40 | "httpreq": "^0.4.24",
41 | "lodash": "^4.17.4",
42 | "q": "^1.5.1",
43 | "request": "^2.83.0",
44 | "string": "^3.3.3",
45 | "validate.js": "^0.12.0",
46 | "winston": "^2.4.0"
47 | },
48 | "devDependencies": {
49 | "chai": "^4.1.2",
50 | "chai-as-promised": "^7.1.1",
51 | "gulp": "git://github.com/gulpjs/gulp#4.0",
52 | "gulp-istanbul": "^1.1.3",
53 | "gulp-mocha": "^5.0.0",
54 | "gulp-verb": "^0.3.0",
55 | "mocha": "^5.0.0",
56 | "nock": "^9.1.6",
57 | "sinon": "^4.2.0",
58 | "template": "^0.17.5"
59 | },
60 | "files": [
61 | "index.js",
62 | "lib/logger.js",
63 | "lib/qrs.js",
64 | "lib/sugar/ep-mime.js",
65 | "lib/sugar/ep-extension.js"
66 | ],
67 | "engines": {
68 | "node": ">=0.8.0"
69 | },
70 | "verb": {
71 | "baseUrl": "https://github.com/stefanwalther/qrs/blob/master/",
72 | "baseImgUrl": "https://raw.githubusercontent.com/stefanwalther/qrs/master/",
73 | "run": true,
74 | "toc": {
75 | "render": true,
76 | "method": "preWrite",
77 | "maxdepth": 2,
78 | "footer": " "
79 | },
80 | "layout": "empty",
81 | "tasks": [
82 | "readme"
83 | ],
84 | "data": {
85 | "twitter": "waltherstefan",
86 | "travis_url": "stefanwalther"
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/test/testSetup.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var fsUtils = require( 'fs-utils' );
3 | var path = require( 'path' );
4 | var _ = require( 'lodash' );
5 |
6 | var TestSetup = function () {
7 | var testConfig = fsUtils.readYAMLSync( path.join( __dirname, './test-config.yml' ) );
8 |
9 | function getTestLoop () {
10 |
11 | var configs = [];
12 |
13 | if ( testConfig.authentication.header.enabled ) {
14 | configs.push( {
15 | 'name': 'Header authentication',
16 | 'config': {
17 | authentication: 'header',
18 | headerKey: testConfig.authentication.header.headerKey,
19 | headerValue: testConfig.authentication.header.headerValue,
20 | virtualProxy: testConfig.authentication.header.virtualProxy,
21 | port: testConfig.authentication.header.port || testConfig.port,
22 | useSSL: _.isBoolean( testConfig.authentication.header.useSSL ) ? testConfig.authentication.header.useSSL : testConfig.useSSL
23 | }
24 | });
25 | }
26 |
27 | if (testConfig.authentication.ntlm.enabled) {
28 |
29 | configs.push({
30 | 'name': 'Ntlm authentication',
31 | 'config': {
32 | virtualProxy: testConfig.authentication.ntlm.virtualProxy,
33 | port: testConfig.authentication.ntlm.port || testConfig.port
34 | }
35 | });
36 | }
37 |
38 | if ( testConfig.authentication.certificates.enabled ) {
39 | configs.push({
40 | 'name': 'Certificates based authentication',
41 | 'config': {
42 | authentication: 'certificates',
43 | headerKey: testConfig.authentication.certificates.headerKey,
44 | headerValue: testConfig.authentication.certificates.headerValue,
45 | useSSL: _.isBoolean(testConfig.authentication.certificates.useSSL) ? testConfig.authentication.certificates.useSSL : testConfig.useSSL,
46 | cert: testConfig.authentication.certificates.cert,
47 | key: testConfig.authentication.certificates.key,
48 | ca: testConfig.authentication.certificates.ca,
49 | passphrase: testConfig.authentication.certificates.passphrase,
50 | virtualProxy: testConfig.authentication.certificates.virtualProxy,
51 | port: testConfig.authentication.certificates.port || testConfig.port
52 |
53 | }
54 | });
55 | }
56 |
57 | return configs;
58 | }
59 |
60 | var getGlobalConfig = function ( ) {
61 |
62 | return {
63 | host: testConfig.host,
64 | useSSL: testConfig.useSSL,
65 | xrfkey: testConfig.xrfkey,
66 | fiddler: testConfig.fiddler,
67 | port: 0
68 | };
69 | };
70 |
71 | return {
72 | globalConfig: getGlobalConfig(),
73 | testLoop: getTestLoop(),
74 | testConfig: testConfig
75 | };
76 |
77 | };
78 |
79 | module.exports = new TestSetup();
80 |
--------------------------------------------------------------------------------
/lib/sugar/ep-extension.js.bak:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require( 'lodash' );
3 | var Q = require( 'q' );
4 |
5 | /**
6 | * Extension plugin.
7 | *
8 | * @param {qrs} base - Base class, instance of `qrs`.
9 | * @api public
10 | */
11 | function Extension ( base ) {
12 |
13 | // Borrowed from http://stackoverflow.com/questions/17251764/lodash-filter-collection-using-array-of-values
14 | _.mixin( {
15 | 'findByValues': function ( collection, property, values ) {
16 | return _.filter( collection, function ( item ) {
17 | return _.contains( values, item[property] );
18 | } );
19 | }
20 | } );
21 |
22 | var defaultFilter = [
23 | 'visualization',
24 | 'visualization-template',
25 | 'mashup',
26 | 'mashup-template'
27 | ];
28 |
29 | /**
30 | * Return all installed extensions. Optionally pass in a filter, to get only returned those extensions matching the given filter.
31 | *
32 | * @param {String[]} [filter] - Optional. Filter installed extensions by `type`. Example: filter = `['visualization', 'visualization-type']` will only return visualization extensions and visualization extension templates.
33 | * @returns {promise}
34 | * @api public
35 | */
36 | this.getInstalled = function ( filter ) {
37 | var defer = Q.defer();
38 | base.get( '/qrs/extension/schema' ).then( function ( data ) {
39 |
40 | if ( filter ) {
41 | defer.resolve( _.findByValues( data, 'type', filter ) );
42 | } else {
43 | defer.resolve( _.findByValues( data, 'type', defaultFilter ) );
44 | }
45 |
46 | }, function ( err ) {
47 | defer.reject( err );
48 | } );
49 |
50 | return defer.promise;
51 | };
52 |
53 | /**
54 | * Same as getInstalled but only returns visualization extensions (type `visualization`).
55 | * @returns {promise}
56 | * @api public
57 | */
58 | this.getInstalledVis = function () {
59 | return this.getInstalled( ['visualization'] );
60 | };
61 |
62 | /**
63 | * Same as `getInstalled` but only returns extensions of type `visualization-template`, which are the templates for the Extension editor in Dev Hub.
64 | * @returns {promise}
65 | * @api public
66 | */
67 | this.getInstalledVisTemplates = function () {
68 | return this.getInstalled( ['visualization-template'] );
69 | };
70 |
71 | /**
72 | * Same as `getInstalled` but only returns extensions of type `mashup`.
73 | * @returns {promise}
74 | * @api public
75 | */
76 | this.getInstalledMashups = function () {
77 | return this.getInstalled( ['mashup'] );
78 | };
79 |
80 | /**
81 | * Same as `getInstalled` but only returns extensions of type `mashup`.
82 | * @returns {promise}
83 | * @api public
84 | */
85 | this.getInstalledMashupTemplates = function () {
86 | return this.getInstalled( ['mashup-template'] );
87 | };
88 |
89 | /**
90 | * Deletes an extension.
91 | * @param {String} name - Name of the extension.
92 | */
93 | this.delete = function ( name ) {
94 | var defer = Q.defer();
95 |
96 | base.delete( 'extension/name' + name ).then( function ( data ) {
97 | console.log('data', data);
98 | defer.resolve();
99 | }, function ( err ) {
100 | defer.reject( err );
101 | });
102 |
103 | return defer.promise;
104 | };
105 |
106 | /**
107 | * Returns whether an extension is installed or not.
108 | * @param name
109 | */
110 | var isInstalled = function ( name ) {
111 |
112 | var defer = Q.defer();
113 | var retVal = {
114 | isInstalled: false,
115 | name: name,
116 | info: {}
117 | };
118 | defer.resolve( retVal );
119 | return defer.promise;
120 | };
121 | }
122 |
123 | module.exports = Extension;
124 |
--------------------------------------------------------------------------------
/lib/sugar/ep-extension.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require( 'lodash' );
3 | var Q = require( 'q' );
4 | var fs = require( 'fs' );
5 | var path = require( 'path' );
6 |
7 | /**
8 | * Extension plugin.
9 | *
10 | * @param {qrs} base - Base class, instance of `qrs`.
11 | * @api public
12 | */
13 | function Extension ( base ) {
14 |
15 | // Borrowed from http://stackoverflow.com/questions/17251764/lodash-filter-collection-using-array-of-values
16 | _.mixin( {
17 | 'findByValues': function ( collection, property, values ) {
18 | return _.filter( collection, function ( item ) {
19 | return _.contains( values, item[property] );
20 | } );
21 | }
22 | } );
23 |
24 | var defaultFilter = [
25 | 'visualization',
26 | 'visualization-template',
27 | 'mashup',
28 | 'mashup-template'
29 | ];
30 |
31 | /**
32 | * Return all installed extensions. Optionally pass in a filter, to get only returned those extensions matching the given filter.
33 | *
34 | * @param {String[]} [filter] - Optional. Filter installed extensions by `type`. Example: filter = `['visualization', 'visualization-type']` will only return visualization extensions and visualization extension templates.
35 | * @returns {promise}
36 | * @api public
37 | */
38 | this.getInstalled = function ( filter ) {
39 | var defer = Q.defer();
40 | base.get( '/qrs/extension/schema' ).then( function ( data ) {
41 |
42 | if ( filter ) {
43 | defer.resolve( _.findByValues( data, 'type', filter ) );
44 | } else {
45 | defer.resolve( _.findByValues( data, 'type', defaultFilter ) );
46 | }
47 |
48 | }, function ( err ) {
49 | defer.reject( err );
50 | } );
51 | return defer.promise;
52 | };
53 |
54 | /**
55 | * Same as getInstalled but only returns visualization extensions (type `visualization`).
56 | * @returns {promise}
57 | * @api public
58 | */
59 | this.getInstalledVis = function () {
60 | return this.getInstalled( ['visualization'] );
61 | };
62 |
63 | /**
64 | * Same as `getInstalled` but only returns extensions of type `visualization-template`, which are the templates for the Extension editor in Dev Hub.
65 | * @returns {promise}
66 | * @api public
67 | */
68 | this.getInstalledVisTemplates = function () {
69 | return this.getInstalled( ['visualization-template'] );
70 | };
71 |
72 | /**
73 | * Same as `getInstalled` but only returns extensions of type `mashup`.
74 | * @returns {promise}
75 | * @api public
76 | */
77 | this.getInstalledMashups = function () {
78 | return this.getInstalled( ['mashup'] );
79 | };
80 |
81 | /**
82 | * Same as `getInstalled` but only returns extensions of type `mashup`.
83 | * @returns {promise}
84 | * @api public
85 | */
86 | this.getInstalledMashupTemplates = function () {
87 | return this.getInstalled( ['mashup-template'] );
88 | };
89 |
90 | /**
91 | * Deletes an extension.
92 | * @param {String} name - Name of the extension.
93 | */
94 | this.delete = function ( name ) {
95 | var defer = Q.defer();
96 |
97 | base.delete( 'extension/name', name )
98 | .then( function ( data ) {
99 | defer.resolve();
100 | }, function ( err ) {
101 | defer.reject( err );
102 | } );
103 |
104 | return defer.promise;
105 | };
106 |
107 | /**
108 | * Uploads an extension.
109 | * @param {String} zipFilePath
110 | * @todo: Handle "Bad request" in case an extension is already existing.
111 | */
112 | this.upload = function ( zipFilePath ) {
113 | var defer = Q.defer();
114 |
115 | var skip = false;
116 | if ( !fs.existsSync( zipFilePath ) ) {
117 | defer.reject( 'File does not exist: ' + zipFilePath );
118 | skip = true;
119 | }
120 | if ( path.extname( zipFilePath ).toLowerCase() !== '.zip' ) {
121 | defer.reject( 'Only .zip files can be uploaded.' );
122 | skip = true;
123 | }
124 |
125 | if (!skip) {
126 | base.postFile( 'qrs/extension/upload', null, zipFilePath )
127 | .then( function ( data ) {
128 | defer.resolve( data ); //Todo: data contains the file buffer, doesn't make a lot of sense returning this data.
129 | }, function ( err ) {
130 | defer.reject( err );
131 | } );
132 | }
133 | return defer.promise;
134 | };
135 |
136 | /**
137 | * Returns whether an extension is installed or not.
138 | * @param name
139 | */
140 | this.isInstalled = function ( name ) {
141 |
142 | var defer = Q.defer();
143 |
144 | var retVal = {
145 | isInstalled: false,
146 | name: name,
147 | info: {}
148 | };
149 | defer.resolve( retVal );
150 | return defer.promise;
151 | };
152 | }
153 |
154 | module.exports = Extension;
155 |
--------------------------------------------------------------------------------
/test/unit/02-qrs.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,beforeEach,it*/
2 | /*jshint -W030,-W117*/
3 |
4 | 'use strict';
5 |
6 | var chai = require( 'chai' );
7 | var chaiAsPromised = require( 'chai-as-promised' );
8 | var expect = chai.expect;
9 | var assert = chai.assert;
10 | var QRS = new require( './../../lib/qrs' );
11 | var nock = require( 'nock' );
12 |
13 | chai.use(chaiAsPromised);
14 |
15 |
16 | describe( 'Unit: qrs', function () {
17 |
18 | var qrs;
19 |
20 | /**
21 | * Reset the configuration before each test.
22 | */
23 | beforeEach( function ( ) {
24 |
25 | var config = {
26 | useSSL: false,
27 | port: 4242,
28 | host: 'myHost',
29 | xrfkey: '123456789ABCDEFG',
30 | virtualProxy: 'sso'
31 | } ;
32 | qrs = new QRS( config );
33 | } );
34 |
35 | it( 'should be properly set up', function ( ) {
36 | assert( typeof(qrs) === 'object' );
37 | assert( typeof(qrs.set) === 'function' );
38 | expect( qrs.getConfig() ).to.not.be.empty;
39 | } );
40 |
41 | it( 'should receive startup params', function ( ) {
42 |
43 | var qrs2 = new QRS( {host: 'testhost'} );
44 | var cfg = qrs2.getConfig();
45 | expect( cfg ).to.exist;
46 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost' );
47 | } );
48 |
49 | it( 'should allow to change params', function ( ) {
50 | var qrs2 = new QRS( {host: 'testhost'} );
51 | qrs2.set( 'host', 'testhost2' );
52 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost2' );
53 | } );
54 |
55 | it( 'should properly create URLs', function () {
56 |
57 | var qrs2 = new QRS(); // Create a new QRS to be independent from any config
58 |
59 | // Default
60 | qrs2.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} );
61 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' );
62 |
63 | // SSL
64 | qrs2.setConfig( {useSSL: true, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} );
65 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'https://myHost/qrs/about/?xrfkey=123456789ABCDEFG' );
66 |
67 | // Virtual Proxy
68 | qrs2.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} );
69 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
70 |
71 | // Port
72 | qrs2.setConfig( {
73 | useSSL: false,
74 | port: 4242,
75 | host: 'myHost',
76 | xrfkey: '123456789ABCDEFG',
77 | virtualProxy: 'sso'
78 | } );
79 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
80 | } );
81 |
82 |
83 | it.skip( 'should fail if METHOD is not in (GET, POST, PUT, DELETE)', function ( ) {
84 | expect( true ).to.equal( false );
85 | done();
86 | } );
87 |
88 | describe( 'qrs.request', function () {
89 | it( 'should resolve properly', function () {
90 |
91 | nock( 'http://mock-host:4242/' )
92 | .get( '/qrs/about/?xrfkey=123456789ABCDEFG' )
93 | .reply( 200, 'Hello World' );
94 |
95 | qrs.setConfig( {
96 | port: 4242,
97 | host: 'mock-host',
98 | xrfkey: '123456789ABCDEFG',
99 | virtualProxy: ''
100 | } );
101 | return expect( qrs.request('GET', 'qrs/about' ) ).to.eventually.be.equal('Hello World');
102 |
103 | } );
104 |
105 | it.skip( 'should reject if config parameters are missing', function ( ) {
106 |
107 | nock( 'http://mock-host:4242/' )
108 | .get( '/qrs/about/?xrfkey=123456789ABCDEFG' )
109 | .reply( 200, 'Hello World' );
110 |
111 | return expect( qrs.request('GET', '/qrs/about') ).to.be.rejected;
112 | } );
113 | } );
114 |
115 | describe( 'qrs.get', function () {
116 | it( 'should resolve for /qrs/about/', function ( ) {
117 | nock( 'http://mock-host:4242/' )
118 | .get( '/qrs/about/?xrfkey=123456789ABCDEFG' )
119 | .reply( 200, 'Hello World' );
120 |
121 | qrs.setConfig( {
122 | port: 4242,
123 | host: 'mock-host',
124 | xrfkey: '123456789ABCDEFG',
125 | virtualProxy: ''
126 | } );
127 | return expect( qrs.get('qrs/about' ) ).to.eventually.be.equal('Hello World');
128 | } );
129 | } );
130 |
131 | describe( 'qrs.post', function () {
132 |
133 | it( 'should resolve for /qrs/whatever', function ( ) {
134 |
135 | nock( 'http://mock-host:4242/' )
136 | .post( '/qrs/whatever/?xrfkey=123456789ABCDEFG' )
137 | .reply( 200, 'Hello World' );
138 |
139 | qrs.setConfig( {
140 | port: 4242,
141 | host: 'mock-host',
142 | xrfkey: '123456789ABCDEFG',
143 | virtualProxy: ''
144 | } );
145 | return expect( qrs.post('qrs/whatever' ) ).to.eventually.be.equal('Hello World');
146 |
147 | } );
148 |
149 | } );
150 |
151 | } );
152 |
--------------------------------------------------------------------------------
/test/unit/01-setup.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,beforeEach,it*/
2 | /*jshint -W030,-W117*/
3 | 'use strict';
4 |
5 | var chai = require( 'chai' );
6 | var assert = chai.assert;
7 | var expect = chai.expect;
8 | var QRS = new require( './../../lib/qrs' );
9 | var testSetup = require( './../testSetup' );
10 |
11 | var globalConfig = testSetup.globalConfig;
12 |
13 | describe( 'Unit: setup', function () {
14 |
15 | var qrs;
16 | /**
17 | * Reset the configuration before each test.
18 | */
19 | beforeEach( function () {
20 |
21 | qrs = new QRS( globalConfig );
22 |
23 | } );
24 |
25 | it( 'should be properly set up', function () {
26 | assert( typeof(qrs) === 'object' );
27 | assert( typeof(qrs.set) === 'function' );
28 | expect( qrs.getConfig() ).to.not.be.empty;
29 | } );
30 |
31 | it( 'should have default options', function () {
32 | var qrsDefault = new QRS( {} );
33 | var c = qrsDefault.getConfig();
34 | expect( c ).to.not.be.empty;
35 | expect( c ).to.have.property( 'host', '127.0.0.1' );
36 | expect( c ).to.have.property( 'useSSL', false );
37 | expect( c ).to.have.property( 'xrfkey', 'ABCDEFG123456789' );
38 | expect( c ).to.have.property( 'authentication', 'windows' );
39 | expect( c ).to.have.property( 'headerKey', '' );
40 | expect( c ).to.have.property( 'headerValue', '' );
41 | expect( c ).to.have.property( 'virtualProxy', '' );
42 | } );
43 |
44 | it( 'should receive startup params', function () {
45 | var qrs2 = new QRS( {host: 'testhost'} );
46 | var cfg = qrs2.getConfig();
47 | expect( cfg ).to.exist;
48 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost' );
49 | } );
50 |
51 | it( 'should allow to change params', function () {
52 | var qrs2 = new QRS( {host: 'testhost'} );
53 | qrs2.set( 'host', 'testhost2' );
54 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost2' );
55 | } );
56 |
57 | describe( 'qrs.getUrl', function () {
58 |
59 | it( 'should properly return URLs: default', function () {
60 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} );
61 | expect( qrs.getUrl( '/qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' );
62 | } );
63 |
64 | it( 'should properly return URLs: including slash', function () {
65 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} );
66 | expect( qrs.getUrl( '/qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' );
67 | } );
68 |
69 | it( 'should properly return URLs: SSL', function () {
70 | qrs.setConfig( {useSSL: true, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} );
71 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'https://myHost/qrs/about/?xrfkey=123456789ABCDEFG' );
72 | } );
73 |
74 | it( 'should properly return URLs: Virtual Proxy', function () {
75 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} );
76 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
77 | } );
78 |
79 | it( 'should properly return URLs: Empty Virtual Proxy', function () {
80 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} );
81 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' );
82 | } );
83 |
84 | it( 'should properly return URLs: Port', function () {
85 | qrs.setConfig( {
86 | useSSL: false,
87 | port: 4242,
88 | host: 'myHost',
89 | xrfkey: '123456789ABCDEFG',
90 | virtualProxy: 'sso'
91 | } );
92 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
93 | } );
94 |
95 | it( 'should allow to pass in port a string or as number', function () {
96 | qrs.setConfig( {
97 | useSSL: false,
98 | port: 4242,
99 | host: 'myHost',
100 | xrfkey: '123456789ABCDEFG',
101 | virtualProxy: 'sso'
102 | } );
103 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
104 |
105 | qrs.setConfig( {
106 | useSSL: false,
107 | port: '4242',
108 | host: 'myHost',
109 | xrfkey: '123456789ABCDEFG',
110 | virtualProxy: 'sso'
111 | } );
112 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
113 | } );
114 |
115 | it( 'should revert to default values if port is not a number', function () {
116 | qrs.setConfig( {
117 | useSSL: false,
118 | port: 'abc',
119 | host: 'myHost',
120 | xrfkey: '123456789ABCDEFG',
121 | virtualProxy: 'sso'
122 | } );
123 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/sso/qrs/about/?xrfkey=123456789ABCDEFG' );
124 | } );
125 |
126 | it( 'should handle URL params when creating the URL', function () {
127 |
128 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} );
129 | expect( qrs.getUrl( 'qrs/about', [{'key': 'myFilter', 'value': 'filtervalue'}, {
130 | 'key': 'param',
131 | 'value': 'paramValue'
132 | }] ) ).to.be.equal( 'http://myHost/sso/qrs/about/?myFilter=filtervalue¶m=paramValue&xrfkey=123456789ABCDEFG' );
133 |
134 | } );
135 |
136 | it( 'should throw an exception if urlParams is not an array', function () {
137 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} );
138 | expect( qrs.getUrl.bind( null, 'qrs/about', {
139 | 'key': 'myFilter',
140 | 'value': 'filterValue'
141 | } ) ).to.throw( 'Parameter urlParams needs to be an array' );
142 | } );
143 |
144 | } );
145 | } );
146 |
--------------------------------------------------------------------------------
/test/e2e/03-ep-extension.e2e.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,it,beforeEach,before,after*/
2 | /* jshint -W030 */
3 | 'use strict';
4 |
5 | var chai = require( 'chai' );
6 | var chaiAsPromised = require( 'chai-as-promised' );
7 | var expect = chai.expect;
8 | var QRS = new require( './../../lib/qrs' );
9 | var extend = require( 'extend-shallow' );
10 | var fsUtils = require( 'fs-utils' );
11 | var path = require( 'path' );
12 | var testSetup = require( './../testSetup' );
13 | var async = require( 'async' );
14 | var extensionSetup = require( './../03-ep-extension.setup' )();
15 |
16 | chai.use( chaiAsPromised );
17 |
18 | var qrs;
19 |
20 | var globalConfig = testSetup.globalConfig;
21 | describe( 'qrs.extension', function () {
22 |
23 | it( 'should be an object', function () {
24 | qrs = new QRS( globalConfig );
25 | expect( qrs.extension ).to.exist;
26 | } );
27 |
28 | testSetup.testLoop.forEach( function ( loopConfig ) {
29 |
30 | describe( 'with ' + loopConfig.name, function () {
31 |
32 | var testConfig = extend( globalConfig, loopConfig.config );
33 |
34 | after( function ( done ) {
35 | console.log( 'after \n' );
36 | //extensionSetup.cleanExtZipFiles( function ( /*err, extensions*/ ) {
37 | // done();
38 | //} );
39 | done();
40 | } );
41 |
42 | before( function ( done ) {
43 | console.log( 'before\n' );
44 | extensionSetup.init( done );
45 | } );
46 |
47 | beforeEach( function ( done ) {
48 | qrs = new QRS( testConfig );
49 | async.map( extensionSetup.extensions, function ( ext, cb ) {
50 | qrs.extension.delete( ext.name )
51 | .then( function ( /*data*/ ) {
52 | //cb( null );
53 | }, function ( err ) {
54 | cb( err );
55 | } );
56 | }, function ( err /*, results */ ) {
57 | if ( err ) {
58 | throw err;
59 | }
60 | done();
61 | } );
62 | } );
63 |
64 | it( 'should return all installed extensions', function () {
65 |
66 | expect( qrs.extension.getInstalled() ).to.be.fulfilled.and.to.be.an.array;
67 |
68 | } );
69 |
70 | it( 'should allow a filter to only return specific types', function () {
71 |
72 | expect( qrs.extension.getInstalled( ['visualization-template'] ) ).to.be.fulfilled.and.to.be.an.array;
73 |
74 | } );
75 |
76 | //Todo: Improve the test => move to unit tests
77 | it( 'should return only extensions of type ', function ( done ) {
78 | qrs.extension.getInstalledVis()
79 | .then( function ( data ) {
80 | expect( data ).to.exist;
81 | expect( data ).to.be.an.array;
82 | }, function ( err ) {
83 | expect( err ).to.not.exist;
84 | } )
85 | .done( function () {
86 | done();
87 | } );
88 | } );
89 |
90 | //Todo: Improve the test => move to unit tests
91 | it( 'should return only extensions of type ', function ( done ) {
92 | qrs.extension.getInstalledVisTemplates()
93 | .then( function ( data ) {
94 | expect( data ).to.exist;
95 | expect( data ).to.be.an.array;
96 | }, function ( err ) {
97 | expect( err ).to.not.exist;
98 | } )
99 | .done( function () {
100 | done();
101 | } );
102 | } );
103 |
104 | //Todo: Improve the test ==> move to unit tests
105 | it( 'should return only extensions of type ', function ( done ) {
106 | qrs.extension.getInstalledMashups()
107 | .then( function ( data ) {
108 | expect( data ).to.exist;
109 | expect( data ).to.be.an.array;
110 | }, function ( err ) {
111 | expect( err ).to.not.exist;
112 | } )
113 | .done( function () {
114 | done();
115 | } );
116 | } );
117 |
118 | //Todo: Improve the test ==> move to unit tests
119 | it( 'should return only extensions of type ', function ( done ) {
120 | qrs.extension.getInstalledMashupTemplates()
121 | .then( function ( data ) {
122 | expect( data ).to.exist;
123 | expect( data ).to.be.an.array;
124 | }, function ( err ) {
125 | expect( err ).to.not.exist;
126 | } )
127 | .done( function () {
128 | done();
129 | } );
130 | } );
131 |
132 | describe( 'qrs.extension.upload', function () {
133 |
134 | it( 'doesn\'t allow upload of non existing files', function () {
135 |
136 | var extPath = path.join( __dirname, './fixtures/extensions/qrs-ABCDEFGHIJKLMNOPQ.zip' );
137 | return expect( qrs.extension.upload( extPath ) ).to.eventually.be.rejectedWith( 'File does not exist: ' + extPath );
138 |
139 | } );
140 |
141 | it( 'doesn\'t allow upload of an extension with a different type than .zip', function () {
142 |
143 | var extPath = path.join( __dirname, './../fixtures/extensions/qrs-sample.7z' );
144 | return expect( qrs.extension.upload( extPath ) ).to.eventually.be.rejectedWith( 'Only .zip files can be uploaded.' );
145 |
146 | } );
147 | } );
148 |
149 | /**
150 | * @todo: Returns "bad request" if extension is already existing.
151 | */
152 | it.skip( 'should allow upload of an extension with absolute path', function ( done ) {
153 | var extPath = path.join( __dirname, './../fixtures/extensions/qrs-sample.zip' );
154 | qrs.extension.upload( extPath )
155 | .then( function ( data ) {
156 | expect( data ).to.exist;
157 | }, function ( err ) {
158 | expect( err ).to.not.exist;
159 | } )
160 | .done( function () {
161 | done();
162 | } );
163 | } );
164 |
165 | describe( 'should allow deletion of an extension', function () {
166 |
167 | before( '', function ( done ) {
168 | //qrs.extension.upload
169 | done();
170 | } );
171 | after( '', function ( done ) {
172 |
173 | done();
174 | } );
175 |
176 | it( '...', function ( done ) {
177 | expect( true ).to.equal( true );
178 | done();
179 | } );
180 | } );
181 |
182 | it.skip( 'should refuse to upload an extension if already existing, even with another type', function ( done ) {
183 | expect( true ).to.equal( false );
184 | done();
185 | } );
186 |
187 | } );
188 | } );
189 | } );
190 |
--------------------------------------------------------------------------------
/lib/sugar/ep-mime.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Q = require( 'q' );
3 | var _ = require( 'lodash' );
4 | var fs = require( 'fs' );
5 |
6 | /**
7 | * Mime type definition
8 | * @typedef {object} mimeTypeDef.
9 | * @property {string} mime - Mime type, e.g. "application/markdown".
10 | * @property {string} extensions - Comma delimited string of supported file extensions, e.g. "md,markdown".
11 | * @property {boolean} additionalHeaders - Additional headers, defaults to null.
12 | * @property {boolean} binary - Whether this is a binary file type or not.
13 | * @api public
14 | */
15 |
16 | /**
17 | * Handle Mime types in QRS.
18 | *
19 | * @param {qrs} base - Base class, instance of `qrs`.
20 | * @constructor
21 | * @api public
22 | */
23 | function Mime ( base ) {
24 |
25 | /**
26 | * Adds a mime type.
27 | *
28 | * When adding the mime type, the following validation logic will be performed:
29 | * - All existing mime types will be grouped by mime, additionalHeaders and binary.
30 | * - If there is already a mime type with the same information compared to the given one, the field extension will be updated.
31 | *
32 | * An example is listed below.
33 | *
34 | *
35 | * ```js
36 | * // -----------------------------------------------------------------
37 | * // Inserting logic
38 | * // ----------------------------------------------------------------- *
39 | *
40 | * // Assuming that the following mime type is already stored:
41 | * {
42 | * extensions: "md"
43 | * mime: "application/markdown",
44 | * additionalHeaders: null,
45 | * binary: false
46 | * }
47 | * // If you add the following mime type
48 | * {
49 | * extensions: "markdown"
50 | * mime: "application/markdown",
51 | * additionalHeaders: null,
52 | * binary: false
53 | * }
54 | * //this would then result into:
55 | * {
56 | * extensions: "md,markdown"
57 | * mime: "application/markdown",
58 | * additionalHeaders: null,
59 | * binary: false
60 | * }
61 | *
62 | * // -----------------------------------------------------------------
63 | * // Adding a mime type:
64 | * // -----------------------------------------------------------------
65 | *
66 | * var mimeType = {
67 | * extensions: "md"
68 | * mime: "application/markdown",
69 | * additionalHeaders: null,
70 | * binary: false
71 | * }
72 | *
73 | * qrs.mime.add( mimeType )
74 | * .then( function (result ) {
75 | * // work with the result
76 | * }, function (err) {
77 | * // error handling
78 | * });
79 | *
80 | * ```
81 | *
82 | * @param {Object} mimeTypeDef
83 | * @param {string} mimeTypeDef.mime - Mime type, e.g. "application/markdown".
84 | * @param {string} mimeTypeDef.extensions - Comma delimited string of supported file extensions, e.g. "md,markdown".
85 | * @param {boolean} mimeTypeDef.additionalHeaders - Additional headers, defaults to null.
86 | * @param {boolean} mimeTypeDef.binary - Whether this is a binary file type or not.
87 | * @return {promise}
88 | * @api public
89 | */
90 | var add = function ( mimeTypeDef ) {
91 |
92 | var defer = Q.defer();
93 | if ( _.isEmpty(mimeTypeDef.mime)) {
94 | defer.reject('Mime type cannot be empty');
95 | }
96 | if ( _.isEmpty(mimeTypeDef.extensions)) {
97 | defer.reject('Extensions cannot be empty');
98 | }
99 | get()
100 | .then( function ( mimeList ) {
101 | var o = getUpdateOrInsert( mimeTypeDef, mimeList );
102 | if ( o.isUpdate ) {
103 | _update( o.def.id, o.def ).then( function ( data ) {
104 | defer.resolve( data );
105 | }, function ( err ) {
106 | defer.reject( err );
107 | } );
108 | } else {
109 | _add( o.def ).then( function ( data ) {
110 | defer.resolve( data );
111 | }, function ( err ) {
112 | defer.reject( err );
113 | } );
114 | }
115 | }, function ( err ) {
116 | defer.reject( err );
117 | } );
118 | return defer.promise;
119 | };
120 | var _add = function ( mimeTypeDef ) {
121 | return base.post( '/qrs/mimetype', null, mimeTypeDef );
122 | };
123 |
124 | /**
125 | * Returns a list of existing mime types.
126 | *
127 | * The list can be filtered by the file-extensions as shown in the example.
128 | *
129 | * ```js
130 | * getMimeTypes( 'html')
131 | * .then( function (data) {
132 | *
133 | * // data now contains an array of mime types where the field extensions contains "html"
134 | * // Results: html, xhtml, etc.
135 | *
136 | * })
137 | * ```
138 | *
139 | * @param {string} filter
140 | * @returns {promise}
141 | * @api public
142 | */
143 | var get = function ( filter ) {
144 |
145 | var queryParams = [];
146 | if ( !_.isEmpty( filter ) ) {
147 | queryParams.push( {'key': 'filter', 'value': filter} );
148 | }
149 | return base.get( '/qrs/mimetype/full', queryParams );
150 | };
151 |
152 | /**
153 | * Adds an array of mime types
154 | * (See `add` for more information about `mimeTypeDef`).
155 | *
156 | * @param {mimeTypeDef[]} mimeTypeDefs - Array of mime type definitions.
157 | * @returns {promise}
158 | * @api public
159 | */
160 | var addMultiple = function ( mimeTypeDefs ) {
161 |
162 | var defer = Q.defer();
163 | var results = [];
164 |
165 | defer.resolve();
166 |
167 | if ( mimeTypeDefs && _.isArray( mimeTypeDefs ) ) {
168 |
169 | var promises = [];
170 | mimeTypeDefs.forEach( function ( mimeTypeDef ) {
171 | promises.push( add.bind( null, mimeTypeDef ) );
172 | } );
173 |
174 | return _.reduce( mimeTypeDefs, function ( memo, value ) {
175 | return memo.then( function () {
176 | return add( value );
177 | } ).then( function ( result ) {
178 | results.push( result );
179 | } );
180 | }, defer.promise ).then( function () {
181 | return results;
182 | } );
183 |
184 | }
185 | };
186 |
187 | /**
188 | * Add mime types defined in a file.
189 | * Every line in the file is defined by the following entries, delimited by a semi-colon (;):
190 | * - extensions - {string} file extension, multiple values separated by a comma, e.g. "md,markdown"
191 | * - mime - {string} Mime type
192 | * - additionalHeaders - {boolean} Additional defined headers, leave blank if unsure
193 | * - binary - {boolean} Whether this is a binary format or not.
194 | *
195 | * ```bash
196 | * md;application/markdown;;false
197 | * yml;text/yml;;false
198 | * woff2;application/font-woff2;;true
199 | * ```
200 | *
201 | * @param {String} filePath - Absolute file path.
202 | * @returns {promise}
203 | * @api public
204 | */
205 | var addFromFile = function ( filePath ) {
206 |
207 | var defer = Q.defer();
208 | if ( !fs.existsSync( filePath ) ) {
209 | defer.reject( 'File does not exist' );
210 | }
211 | var data = fs.readFileSync( filePath, {encoding: 'utf-8'} );
212 |
213 | var lines = data.match( /[^\r\n]+/g );
214 | var mimeTypeDefs = [];
215 | lines.forEach( function ( line ) {
216 | var items = line.split( ';' );
217 | mimeTypeDefs.push( {
218 | 'extensions': items[0],
219 | 'mime': items[1],
220 | 'additionalHeaders': _.isEmpty( items[2] ) ? null : items[2],
221 | 'binary': items[4] === 'true'
222 | } );
223 | } );
224 | addMultiple( mimeTypeDefs )
225 | .then( function ( data ) {
226 | defer.resolve( data );
227 | }, function ( err ) {
228 | defer.reject( err );
229 | } );
230 | return defer.promise;
231 | };
232 |
233 | /**
234 | * Delete a mime entry from the Qlik Sense Repository by its given Id.
235 | * @param {String} id
236 | * @returns {promise}
237 | * @api public
238 | */
239 | var deleteById = function ( id ) {
240 | return base.delete( '/qrs/mimetype', id );
241 | };
242 |
243 | var _update = function ( id, mimeTypeDef ) {
244 | return base.put( '/qrs/mimetype', id, null, mimeTypeDef );
245 | };
246 |
247 | var createExport = function ( filePath ) {
248 |
249 | var defer = Q.defer();
250 | base.get( '/qrs/mimetype/full' )
251 | .then( function ( data ) {
252 |
253 | var s = '';
254 | for ( var i = 0; i < data.length; i++ ) {
255 | s += data[i].extensions + ';' ;
256 | s += data[i].mime + ';';
257 | s += ((!_.isEmpty(data[i].additionalHeaders)) ? data[i].additionalHeaders : '') + ';';
258 | s += data[i].binary + '\n';
259 | }
260 | fs.writeFile( filePath, s, function ( err ) {
261 | if ( err ) {
262 | defer.reject( err );
263 | } else {
264 | defer.resolve( filePath );
265 | }
266 | } );
267 |
268 | }, function ( err ) {
269 | defer.reject( err );
270 | } );
271 | return defer.promise;
272 | };
273 |
274 | var createExportPerFileExt = function ( filePath ) {
275 | var defer = Q.defer();
276 | base.get( '/qrs/mimetype/full' )
277 | .then( function ( data ) {
278 |
279 | var ext = [];
280 | for ( var i = 0; i < data.length; i++ ) {
281 | var e = data[i].extensions.split(',');
282 | ext = ext.concat(e);
283 | }
284 |
285 | fs.writeFile( filePath, JSON.stringify(ext), function ( err ) {
286 | if ( err ) {
287 | defer.reject( err );
288 | } else {
289 | defer.resolve( filePath );
290 | }
291 | } );
292 |
293 | }, function ( err ) {
294 | defer.reject( err );
295 | } );
296 | return defer.promise;
297 | };
298 |
299 | /**
300 | * Returns whether the mime type already exists or not.
301 | *
302 | * @param {mimeTypeDef} mimeTypeDef
303 | * @returns {object} result - Returned result.
304 | * @returns {boolean} result.isUpdate - Whether to update or add.
305 | * @api public
306 | */
307 | var getUpdateOrInsert = function ( mimeTypeDef, listMimeTypes ) {
308 |
309 | var result = {
310 | isUpdate: false,
311 | def: {}
312 | };
313 |
314 | var tmp = _.filter( listMimeTypes, function ( m ) {
315 | return m.mime === mimeTypeDef.mime && m.binary === mimeTypeDef.binary || false && ((m.additionalHeaders || null) === (mimeTypeDef.additionalHeaders || null));
316 | } );
317 |
318 | if ( tmp.length === 1 ) {
319 | result.isUpdate = true;
320 | var updatedDef = tmp[0];
321 | updatedDef.extensions = _.uniq( updatedDef.extensions.split( ',' ).concat( mimeTypeDef.extensions.split( ',' ) ) ).join( ',' );
322 | result.def = updatedDef;
323 | } else if ( tmp.length === 0 ) {
324 | result.def = mimeTypeDef;
325 | result.isUpdate = false;
326 | } else if ( tmp.length > 1 ) {
327 | throw new Error( 'More than on mime type found to update' );
328 | }
329 |
330 | return result;
331 | };
332 |
333 | return {
334 | add: add,
335 | addFromFile: addFromFile,
336 | addMultiple: addMultiple,
337 | createExport: createExport,
338 | createExportPerFileExt: createExportPerFileExt,
339 | get: get,
340 | getUpdateOrInsert: getUpdateOrInsert,
341 | deleteById: deleteById
342 | };
343 | }
344 |
345 | module.exports = Mime;
346 |
347 |
348 |
349 |
--------------------------------------------------------------------------------
/test/e2e/04-ep-mime.e2e.spec.js:
--------------------------------------------------------------------------------
1 | /*global describe,it,beforeEach,after*/
2 | /*jshint -W030,-W117*/
3 | 'use strict';
4 |
5 | var chai = require( 'chai' );
6 | var expect = chai.expect;
7 | var assert = chai.assert;
8 | var QRS = new require( './../../lib/qrs' );
9 | var extend = require( 'extend-shallow' );
10 | var fsUtils = require( 'fs-utils' );
11 | var path = require( 'path' );
12 | var testSetup = require( './../testSetup' );
13 | var _ = require( 'lodash' );
14 | var Q = require( 'q' );
15 |
16 | var qrs;
17 | var globalConfig = testSetup.globalConfig;
18 |
19 | describe( 'sugar-plugin: ep-mime', function () {
20 |
21 | testSetup.testLoop.forEach( function ( testLoopConfig ) {
22 |
23 | describe( 'with ' + testLoopConfig.name, function ( ) {
24 |
25 | var testConfig = extend( globalConfig, testLoopConfig.config );
26 | var idsToDelete = [];
27 | beforeEach( function ( done ) {
28 | qrs = new QRS( testConfig );
29 | done();
30 | } );
31 |
32 | afterEach( function ( done ) {
33 |
34 | if ( idsToDelete && idsToDelete.length > 0 ) {
35 | var promises = [];
36 | _.each( idsToDelete, function ( id ) {
37 | promises.push( qrs.mime.deleteById.bind( null, id ) );
38 | } );
39 | Q.all( promises )
40 | .then( function ( data ) {
41 | idsToDelete.length = 0;
42 | done();
43 | } );
44 | } else {
45 | done();
46 | }
47 | } );
48 |
49 | after( function ( done ) {
50 | fsUtils.del( ['./test/mimetypes.text', './test/mimetypes_single.json'], function () {
51 | done();
52 | } );
53 | } );
54 |
55 | it( 'should return the existing mime types', function ( done ) {
56 | qrs.mime.get()
57 | .then( function ( data ) {
58 | expect( data ).to.exist;
59 | expect( data ).to.be.an.array;
60 | } )
61 | .done( function () {
62 | done();
63 | } );
64 | } );
65 |
66 | it( 'should filter for mime types', function ( done ) {
67 | /*jshint -W109 */
68 | qrs.mime.get( "extensions so 'html'" )
69 | /*jshint +W109 */
70 | .then( function ( data ) {
71 | expect( data ).to.exist;
72 | expect( data ).to.not.be.empy;
73 | expect( data ).to.be.an.array;
74 | expect( data ).to.have.length.above( 1 );
75 | } )
76 | .done( function () {
77 | done();
78 | } );
79 | } );
80 |
81 | it( 'filter for unknown should return nothing', function ( done ) {
82 | /*jshint -W109 */
83 | qrs.mime.get( "extensions so 'abcdefg'" )
84 | /*jshint +W109 */
85 | .then( function ( data ) {
86 | expect( data ).to.exist;
87 | expect( data ).to.not.be.empy;
88 | expect( data ).to.be.an.array;
89 | expect( data ).to.have.length( 0 );
90 | } )
91 | .done( function () {
92 | done();
93 | } );
94 | } );
95 |
96 | it( 'can create an export', function ( done ) {
97 | qrs.mime.createExport( path.resolve( './test/mimetypes.text' ) )
98 | .then( function ( path ) {
99 | expect( path ).to.exist;
100 | } )
101 | .done( function () {
102 | done();
103 | } );
104 | } );
105 |
106 | it( 'can create an export (per file extension)', function ( done ) {
107 | qrs.mime.createExportPerFileExt( path.resolve( './test/mimetypes_single.json' ) )
108 | .then( function ( path ) {
109 | expect( path ).to.exist;
110 | } )
111 | .done( function () {
112 | done();
113 | } );
114 | } );
115 |
116 | describe( 'returns either objects to be updated or added', function () {
117 |
118 | /*jshint -W109*/
119 | var existingTypes = [
120 | {
121 | "id": "05750907-1728-46a5-b763-14d348208bf3",
122 | "createdDate": "2015-09-02T22:09:14.104Z",
123 | "modifiedDate": "2015-09-02T22:09:14.104Z",
124 | "modifiedByUserName": "INTERNAL\\bootstrap",
125 | "mime": "application/xhtml+xml",
126 | "extensions": "xhtml,xht",
127 | "additionalHeaders": null,
128 | "binary": false,
129 | "privileges": null,
130 | "schemaPath": "MimeType"
131 | },
132 | {
133 | "id": "49974f33-31c2-4732-b057-7acb8f5303a0",
134 | "createdDate": "2015-09-02T22:09:14.104Z",
135 | "modifiedDate": "2015-09-02T22:09:14.104Z",
136 | "modifiedByUserName": "INTERNAL\\bootstrap",
137 | "mime": "text/html;charset=utf-8",
138 | "extensions": "html,htm",
139 | "additionalHeaders": "X-UA-Compatible:IE=edge",
140 | "binary": false,
141 | "privileges": null,
142 | "schemaPath": "MimeType"
143 | }
144 | ];
145 | /*jshint +W109*/
146 |
147 | it( 'returns the object to be updated', function () {
148 | var r = qrs.mime.getUpdateOrInsert( {
149 | 'extensions': 'foo',
150 | 'mime': 'text/html;charset=utf-8',
151 | 'binary': false
152 | }, existingTypes );
153 | expect( r.isUpdate ).to.equal( true );
154 | expect( r.def ).to.be.an.object;
155 | expect( r.def ).to.not.be.an.array;
156 | expect( r.def.extensions ).to.be.equal( 'html,htm,foo' );
157 | expect( r.def.mime ).to.be.equal( 'text/html;charset=utf-8' );
158 | } );
159 |
160 | it( 'adds a new entry', function ( done ) {
161 | qrs.mime.add( {
162 | 'extensions': 'foo',
163 | 'mime': 'text/foo',
164 | 'additionalHeaders': null,
165 | 'binary': false
166 | } ).then( function ( data ) {
167 | expect( data ).to.exist;
168 | expect( data ).to.have.property( 'id' );
169 | idsToDelete.push( data.id );
170 | }, function ( err ) {
171 | expect( err ).to.not.exist;
172 | } )
173 | .done( function () {
174 | done();
175 | } );
176 | } );
177 |
178 | it( 'should reject to add entries if mime is empty', function ( done ) {
179 | qrs.mime.add( {
180 | 'extensions': 'foo',
181 | 'mime': '',
182 | 'additionalHeaders': null,
183 | 'binary': false
184 | } )
185 | .then( function ( /*data*/ ) {
186 | }, function ( err ) {
187 | expect( err ).to.exist;
188 | expect( err ).to.be.equal( 'Mime type cannot be empty' );
189 | } )
190 | .done( function () {
191 | done();
192 | } );
193 | } );
194 |
195 | it( 'should reject to add entries if extensions is empty', function ( done ) {
196 | qrs.mime.add( {
197 | 'extensions': '',
198 | 'mime': 'foo/bar',
199 | 'additionalHeaders': null,
200 | 'binary': false
201 | } )
202 | .then( function ( /*data*/ ) {
203 | }, function ( err ) {
204 | expect( err ).to.exist;
205 | expect( err ).to.be.equal( 'Extensions cannot be empty' );
206 | } )
207 | .done( function () {
208 | done();
209 | } );
210 | } );
211 |
212 | it( 'adds multiple entries', function ( done ) {
213 | qrs.mime.addMultiple( [{
214 | 'extensions': 'foo',
215 | 'mime': 'text/foo',
216 | 'additionalHeaders': null,
217 | 'binary': false
218 | }, {
219 | 'extensions': 'bar',
220 | 'mime': 'text/bar',
221 | 'additionalHeaders': null,
222 | 'binary': false
223 | }] )
224 | .then( function ( data ) {
225 | expect( data ).to.exist;
226 | expect( data ).to.be.an.array;
227 | data.forEach( function ( item ) {
228 | idsToDelete.push( item.id );
229 | } );
230 | }, function ( err ) {
231 | assert( true, err );
232 | } )
233 | .done( function () {
234 | done();
235 | } );
236 | } );
237 |
238 | //Todo: Test should be done once, and not for all authentication scenarios
239 | it( 'add multiple entries should throw an error if server is not available', function ( done ) {
240 | var config = JSON.parse( JSON.stringify( testConfig ) ); // clone the object
241 | config.host = 'not_a_server';
242 | var qrs2 = new QRS( config );
243 | qrs2.mime.addMultiple( [{
244 | 'extensions': 'foo',
245 | 'mime': 'text/foo',
246 | 'additionalHeaders': null,
247 | 'binary': false
248 | }, {
249 | 'extensions': 'bar',
250 | 'mime': 'text/bar',
251 | 'additionalHeaders': null,
252 | 'binary': false
253 | }] )
254 | .then( function ( data ) {
255 | expect( data ).to.not.exist;
256 | }, function ( err ) {
257 | expect( err ).to.exist;
258 | } )
259 | .done( function () {
260 | done();
261 | } );
262 | } );
263 |
264 | it( 'updates existing entries', function ( done ) {
265 | qrs.mime.addMultiple( [{
266 | 'extensions': 'foo',
267 | 'mime': 'text/foobar',
268 | 'additionalHeaders': null,
269 | 'binary': false
270 | }, {
271 | 'extensions': 'bar',
272 | 'mime': 'text/foobar',
273 | 'additionalHeaders': null,
274 | 'binary': false
275 | }] )
276 | .then( function ( data ) {
277 | expect( data ).to.exist;
278 | expect( data ).to.be.an.array;
279 | _.each( data, function ( item ) {
280 | expect( item ).to.have.property( 'extensions' );
281 | expect( item ).to.have.property( 'mime' );
282 | expect( item ).to.have.property( 'additionalHeaders' );
283 | expect( item ).to.have.property( 'binary' );
284 | expect( item.mime ).to.be.equal( 'text/foobar' );
285 | expect( item.additionalHeaders ).to.be.null;
286 | expect( item.binary ).to.be.equal( false );
287 | idsToDelete.push( data.id );
288 | } );
289 | //Todo: We have a cleanup error here, check this out
290 | //expect(data[0].extensions ).to.be.equal('foo');
291 | //expect(data[1].extensions ).to.be.equal('foo,bar');
292 | }, function ( err ) {
293 | expect( err ).to.not.exist;
294 | } )
295 | .done( function () {
296 | done();
297 | } );
298 | } );
299 |
300 | it( 'adds multiple entries from file (update)', function ( done ) {
301 |
302 | var sourceFile = path.resolve( './test/fixtures/foobar.txt' );
303 | qrs.mime.addFromFile( sourceFile )
304 | .then( function ( data ) {
305 | expect( data ).to.exist;
306 | expect( data ).to.be.an.array;
307 | expect( data ).to.have.length( 2 );
308 | _.each( data, function ( item ) {
309 | expect( item ).to.have.property( 'extensions' );
310 | expect( item ).to.have.property( 'mime' );
311 | expect( item ).to.have.property( 'additionalHeaders' );
312 | expect( item ).to.have.property( 'binary' );
313 | expect( item.mime ).to.be.equal( 'text/foobar' );
314 | expect( item.additionalHeaders ).to.be.null;
315 | expect( item.binary ).to.be.equal( false );
316 | idsToDelete.push( data.id );
317 | } );
318 | //expect(data[0].extensions ).to.be.equal('foo');
319 | //expect(data[1].extensions ).to.be.equal('foo,bar');
320 | }, function ( err ) {
321 | expect( err ).to.not.exist;
322 | } )
323 | .done( function () {
324 | done();
325 | } );
326 | } );
327 |
328 | it( 'adds multiple entries from file (update + insert)', function ( done ) {
329 |
330 | var sourceFile = path.resolve( './test/fixtures/foobarbaz.txt' );
331 | qrs.mime.addFromFile( sourceFile )
332 | .then( function ( data ) {
333 | expect( data ).to.exist;
334 | expect( data ).to.be.an.array;
335 | expect( data ).to.have.length( 3 );
336 | _.each( data, function ( item ) {
337 | expect( item ).to.have.property( 'extensions' );
338 | expect( item ).to.have.property( 'mime' );
339 | expect( item ).to.have.property( 'additionalHeaders' );
340 | expect( item ).to.have.property( 'binary' );
341 | expect( item.additionalHeaders ).to.be.null;
342 | expect( item.binary ).to.be.equal( false );
343 | idsToDelete.push( data.id );
344 | } );
345 | idsToDelete.push( data.id );
346 | }, function ( err ) {
347 | expect( err ).to.not.exist;
348 | } )
349 | .done( function () {
350 | done();
351 | } );
352 | } );
353 |
354 | it( 'adding multiple entries from file should be able to fail (wrong entries)', function ( done ) {
355 | var sourceFile = path.resolve( './test/fixtures/error.txt' );
356 | qrs.mime.addFromFile( sourceFile )
357 | .then( function ( /*data*/ ) {
358 | }, function ( err ) {
359 | expect( err ).to.exist;
360 | expect( err ).to.be.equal( 'Mime type cannot be empty' );
361 | } )
362 | .done( function () {
363 | done();
364 | } );
365 | } );
366 |
367 | it( 'adding multiple entries from file should be able to fail (server unavailable)', function ( done ) {
368 | var config = JSON.parse( JSON.stringify( testConfig ) ); // clone the object
369 | config.host = 'not_a_server';
370 | var qrs2 = new QRS( config );
371 | var sourceFile = path.resolve( './test/fixtures/foobarbaz.txt' );
372 | qrs2.mime.addFromFile( sourceFile )
373 | .then( function ( /*data*/ ) {
374 | }, function ( err ) {
375 | expect( err ).to.exist;
376 | } )
377 | .done( function () {
378 | done();
379 | } );
380 | } );
381 | } );
382 |
383 | });
384 |
385 | });
386 | } );
387 |
--------------------------------------------------------------------------------
/lib/qrs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var extend = require( 'extend-shallow' );
3 | var _ = require( 'lodash' );
4 | var Q = require( 'q' );
5 | var request = require( 'request' );
6 | var S = require( 'string' );
7 | var fs = require( 'fs' );
8 | var path = require( 'path' );
9 | var glob = require( 'glob' );
10 | //var configValidator = require( './config-validator' );
11 |
12 | /**
13 | * Work with Qlik Sense's REST based Repository API (qrs) from within node.js.
14 | *
15 | * **Configuration options:**
16 | * ```js
17 | * var QRS = require('qrs');
18 | * var config = {
19 | host: '127.0.0.1',
20 | useSSL: false,
21 | xrfkey: 'ABCDEFG123456789',
22 | authentication: 'windows',
23 | headerKey: '',
24 | headerValue: '',
25 | virtualProxy: ''
26 | };
27 | *
28 | * var qrs = new QRS( config );
29 | *
30 | * ```
31 | *
32 | * @param {Object} `qrsConfig` Global configuration options
33 | *
34 | * @api public
35 | */
36 | var qrs = function qrs ( qrsConfig ) {
37 |
38 | var that = this;
39 | this.config = null;
40 | this.defaultConfig = {
41 | host: '127.0.0.1',
42 | useSSL: false,
43 | xrfkey: 'ABCDEFG123456789',
44 | authentication: 'windows',
45 | headerKey: '',
46 | headerValue: '',
47 | virtualProxy: ''
48 | };
49 | if ( qrsConfig && !_.isEmpty( qrsConfig ) ) {
50 | this.config = extend( that.defaultConfig, qrsConfig );
51 | }
52 |
53 | /**
54 | * (Internal) generic method to send requests to QRS.
55 | * Typically this method is only used internally, use `get`, `post`, `put` or `delete`.
56 | *
57 | * ```js
58 | * var QRS = require('qrs');
59 | *
60 | * var qrsConfig = ...; // Set configuration options
61 | * var qrs = new QRS( qrsConfig );
62 | *
63 | * qrs.request( 'GET', 'qrs/about', null, null)
64 | * .then( function( data ) {
65 | * console.log( 'about', data );
66 | * }, function ( err ) {
67 | * console.error( 'An error occurred: ', err);
68 | * });
69 | * ```
70 | *
71 | * @param {String} `method` Http method, can be `GET`, `POST`, `PUT` or `DELETE` (defaults to `GET`).
72 | * @param {String} `endpoint` Endpoint to be used. Check the online documentation of the Qlik Sense Repository API to get a list of all endpoints available.
73 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`.
74 | * @param {Object} `jsonBody` JSON object to be used as the body for the Http request.
75 | * @param {String} `body` Body, if not defined as Json object, body needs to be passed as a buffer to e.g. upload a file.
76 | * @param {Object} `additionalRequestOptions` Additional request options.
77 | * @param {Object} `additionalHeaders` Additional headers.
78 | * @returns {promise} Returns a promise.
79 | * @api public
80 | */
81 | this.request = function ( method, endpoint, urlParams, jsonBody, body, additionalRequestOptions, additionalHeaders ) {
82 |
83 | var defer = Q.defer();
84 | var validConfig = _validateConfig();
85 |
86 | if ( !validConfig ) {
87 | defer.reject( {error: {errorMsg: 'Invalid configuration', errSource: 'qrs.request'}} );
88 | } else {
89 |
90 | var url = this.getUrl( S( endpoint ).chompLeft( '/' ), urlParams );
91 | var headers = _getHeaders( additionalHeaders );
92 |
93 | var requestOptions = {
94 | method: method || 'GET',
95 | url: url,
96 | headers: headers,
97 | proxy: that.config.fiddler ? 'http://127.0.0.1:8888' : null,
98 | timeout: 2000
99 | };
100 |
101 | if ( _.isObject( jsonBody ) ) {
102 | requestOptions.json = jsonBody;
103 | }
104 | if ( _.isObject( body ) ) {
105 | requestOptions.body = body;
106 | }
107 |
108 | //Todo: Encapsulate cert-file loading.
109 | //Todo: Support default local certificates.
110 | if ( that.config.authentication === 'certificates' ) {
111 | /*jshint ignore:start*/
112 | if ( that.config['cert'] ) {
113 | requestOptions.cert = (typeof that.config['cert'] === 'object' ? that.config['cert'] : fs.readFileSync( that.config['cert'] ) );
114 | }
115 | if ( that.config['key'] ) {
116 | requestOptions.key = (typeof that.config['key'] === 'object' ? that.config['key'] : fs.readFileSync( that.config['key'] ) );
117 | }
118 | if ( that.config['ca'] ) {
119 | requestOptions.ca = (typeof that.config['ca'] === 'object' ? that.config['ca'] : fs.readFileSync( that.config['ca'] ) );
120 | }
121 | /*jshint ignore:end*/
122 | if ( that.config.passphrase && !_.isEmpty( that.config.passphrase ) ) {requestOptions.passphrase = that.config.passphrase;}
123 | }
124 |
125 | requestOptions = _.extend( requestOptions, additionalRequestOptions || {} );
126 |
127 | request( requestOptions, function ( error, response, responseBody ) {
128 |
129 | //Todo: encapsulate content fetching
130 | if ( error || (response.statusCode < 200 || response.statusCode > 299) ) {
131 | defer.reject( {
132 | error: error,
133 | response: response
134 | } );
135 | } else {
136 | var r = null;
137 | if ( response.statusCode !== 204 ) {
138 | if ( _.isObject( responseBody ) ) {
139 | r = responseBody;
140 | } else {
141 | try {
142 | r = JSON.parse( responseBody );
143 | } catch ( e ) {
144 | r = responseBody;
145 | }
146 | }
147 | }
148 | defer.resolve( r );
149 | }
150 | }
151 | );
152 | }
153 | return defer.promise;
154 | };
155 |
156 | /**
157 | * Same as `request()` but with `method: 'GET'`.
158 | *
159 | * ```js
160 | * qrs.get( 'qrs/about')
161 | * .then( function ( data) {
162 | * console.log('about: ', data );
163 | * }, function ( err ) {
164 | * console.error( err );
165 | * });
166 | * ```
167 | * @param {String} `endpoint` QRS endpoint to be used.
168 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`.
169 | * @returns {promise} Returns a promise.
170 | * @api public
171 | */
172 | this.get = function ( endpoint, urlParams ) {
173 | return this.request( 'GET', endpoint, urlParams );
174 | };
175 |
176 | /**
177 | * Same as `request()` but with `method: 'POST'`.
178 | *
179 | * @param {String} `endpoint` QRS endpoint to be used.
180 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`.
181 | * @param {Object} `jsonBody` Body to be posted, defined as JSON object.
182 | * @returns {promise} Returns a promise.
183 | * @api public
184 | */
185 | this.post = function ( endpoint, urlParams, jsonBody ) {
186 | return this.request( 'POST', endpoint, urlParams, jsonBody );
187 | };
188 |
189 | /**
190 | * Post a file, actually same as `post()`, instead of posting a JSON body, posts a file buffer.
191 | *
192 | * @param {String} `endpoint` QRS endpoint to be used.
193 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`
194 | * @param {String} filePath Absolute or relative file path.
195 | * @returns {promise} Returns a promise.
196 | * @api public
197 | */
198 | this.postFile = function ( endpoint, urlParams, filePath ) {
199 |
200 | var fileBuffer = fs.readFileSync( filePath );
201 | var additionalHeaders = {};
202 | additionalHeaders['Content-Type'] = 'application/octet-stream';
203 | var additionalRequestOptions = {
204 | 'encoding': null
205 | };
206 | return this.request( 'POST', endpoint, urlParams, null, fileBuffer, additionalRequestOptions, additionalHeaders );
207 | };
208 |
209 | /**
210 | * Same as `request()` but with `method: 'PUT'`.
211 | *
212 | * @api public
213 | */
214 | this.put = function ( endpoint, id, urlParams, jsonBody ) {
215 | var finalEndpoint = S( endpoint ).ensureRight( '/' ) + id;
216 | return this.request( 'PUT', finalEndpoint, urlParams, jsonBody );
217 | };
218 |
219 | /**
220 | * Same as `request()` but with `method: 'DELETE'`.
221 | *
222 | * @api public
223 | */
224 | this.delete = function ( endpoint, id, urlParams ) {
225 | var finalEndpoint = S( endpoint ).ensureRight( '/' ) + id;
226 | return this.request( 'DELETE', finalEndpoint, urlParams );
227 | };
228 |
229 | /**
230 | * Return the Url for the REST call considering the given configuration options
231 | *
232 | * @param {string} `endpoint` Endpoint for the qrs call.
233 | * @param {Object[]} `urlParams` Additional URL parameters as key/value array.
234 | * @param {String} `urlParam.key` Key.
235 | * @param {Object} `urlParam.value` Value.
236 | * @return {String} The constructed Url.
237 | * @api public
238 | */
239 | this.getUrl = function ( endpoint, urlParams ) {
240 |
241 | if ( !_.isEmpty( urlParams ) && !_.isArray( urlParams ) ) {
242 | throw new Error( 'Parameter urlParams needs to be an array' );
243 | }
244 | var params = urlParams || [];
245 |
246 | var url = ((that.config.useSSL) ? 'https://' : 'http://'); // http://
247 | url += that.config.host; // http://host
248 | url += (that.config.port && !isNaN( that.config.port ) && that.config.port !== 0) ? ':' + that.config.port : ''; // http[s]://host[:port]
249 | url += '/'; // http[s]://host[:port]/
250 | url += ((that.config.virtualProxy && !_.isEmpty( that.config.virtualProxy )) ? that.config.virtualProxy + '/' : ''); // http[s]://host[:port]/[vp/]
251 | url += S( endpoint ).chompLeft( '/' );
252 | url += '/?';
253 |
254 | params.push( {'key': 'xrfkey', 'value': that.config.xrfkey} );
255 | params.forEach( function ( param ) { // parameters
256 | url += param.key + '=' + param.value + '&';
257 | } );
258 | url = S( url ).chompRight( '&' ).s;
259 |
260 | return url;
261 | };
262 |
263 | /**
264 | * Set global configurations options for qrs. Can be used to change the configuration options after `qrs` has been initialized.
265 | *
266 | * ```js
267 | * var QRS = require('qrs');
268 | * var configCert = {
269 | * authentication: 'certificates',
270 | * ...
271 | * };
272 | * var qrs = new QRS( configCert );
273 | *
274 | * // Talking to the server using certificates
275 | * qrs.get('qrs/about', function( result ) {
276 | * // ...
277 | * });
278 | *
279 | * // Change configuration options, e.g.
280 | * var configHeader = {
281 | * authentication: 'header',
282 | * ...
283 | * };
284 | *
285 | * qrs.setConfig( configHeader);
286 | *
287 | * // Talking to the server, now using header authentication.
288 | * qrs.get('qrs/about', function( result ) {
289 | * // ...
290 | * });
291 | * ```
292 | *
293 | * @param {Object} `qrsConfig` Global configuration options
294 | * @api public
295 | */
296 | this.setConfig = function ( qrsConfig ) {
297 | if ( typeof qrsConfig !== 'undefined' && !_.isEmpty( qrsConfig ) ) {
298 | that.config = extend( that.defaultConfig, qrsConfig );
299 | }
300 | return that.config;
301 | };
302 |
303 | /**
304 | * Return the current configuration options.
305 | *
306 | * ```js
307 | * var QRS = require('qrs');
308 | * var config = {
309 | * host: 'myserver.domain.com';
310 | * };
311 | * var qrs = new QRS( config );
312 | * var host = qrs.getConfig( 'host' );
313 | * console.log(host); //<== 'myserver.domain.com'
314 | * ```
315 | *
316 | * @returns {qrsConfig} `qrsConfig` Configuration object.
317 | * @api public
318 | */
319 | this.getConfig = function () {
320 | return that.config || that.defaultConfig;
321 | };
322 |
323 | /**
324 | * Change a single configuration property.
325 | *
326 | * ```js
327 | * var QRS = require('qrs');
328 | * var config = {
329 | * host: 'myserver.domain.com';
330 | * };
331 | * var qrs = new QRS( config );
332 | *
333 | * qrs.get('qrs/about', function( result ) {
334 | * // about from myserver.domain.com
335 | * });
336 | *
337 | * qrs.set('host', 'mysecondserver.domain.com');
338 | * qrs.get('qrs/about', function( result ) {
339 | * // about from mysecondserver.domain.com
340 | * });
341 | * ```
342 | *
343 | * @param {String} `key` Key of the property
344 | * @param {Object} `val` Value
345 | * @api public
346 | */
347 | this.set = function ( key, val ) {
348 | that.config[key] = val;
349 | };
350 |
351 | /**
352 | * Retrieve a single configuration property.
353 | *
354 | * @param {String} `key` Key of the property
355 | * @returns {Object} Value of the requested property, otherwise undefined.
356 | * @api public
357 | */
358 | this.getConfigValue = function ( key ) {
359 | return that.config[key];
360 | };
361 |
362 | // ****************************************************************************************
363 | // Plugins
364 | // ****************************************************************************************
365 |
366 | /**
367 | * Returns an array of loaded plugins. Use `registerPlugin()` to load a plugin.
368 | *
369 | * @type {Array}
370 | * @api public
371 | */
372 | this.plugins = [];
373 |
374 | /**
375 | * Register a plugin to work with the base class of `qrs`.
376 | * Have a look at some of the already existing plugins like `./lib/sugar/ep-mime.js`
377 | *
378 | *
379 | * ```js
380 | *
381 | * // -----------------------------------------------------------
382 | * // Define the plugin.
383 | * // ~~
384 | * // Purpose: Do something great with extensions.
385 | * // Filename: ep-extension.js
386 | * // -----------------------------------------------------------
387 | *
388 | * function Extension ( base ) {
389 | *
390 | * function doSomething() {
391 | * base.get( 'qrs/extension/schema')
392 | * .then( function( result ) {
393 | * // result now holding the result from the server
394 | * }, function (err) {
395 | * // error handling
396 | * });
397 | *
398 | * return {
399 | * doSomething: doSomething
400 | * };
401 | * }
402 | *
403 | * module.exports = Extension;
404 | *
405 | * // -----------------------------------------------------------
406 | * // Register and use it
407 | * // -----------------------------------------------------------
408 | *
409 | * var qrs = new QRS( config );
410 | * qrs.registerPlugin('./ep-extension.js');
411 | *
412 | * // Use the plugin
413 | * qrs.extension.upload( myFile, function( result ) {
414 | * // The file has been uploaded
415 | * });
416 | *
417 | * ```
418 | *
419 | * @param {Object} plugin
420 | * @api public
421 | */
422 | this.registerPlugin = function ( plugin ) {
423 | if ( !this[plugin.name.toLowerCase()] ) {
424 | /*jshint -W055 */
425 | var o = new plugin( this );
426 | /*jshint +W055 */
427 | this.plugins[plugin.name.toLowerCase()] = o;
428 | this[plugin.name.toLowerCase()] = o;
429 | } else {
430 | throw new Error( 'Plugin cannot be registered. Namespace for qrs.' + plugin.name.toLowerCase() + ' is already reserved.' );
431 | }
432 | };
433 |
434 | var matches = glob.sync( path.join( __dirname, './sugar/ep-*.js' ) );
435 | for ( var i = 0; i < matches.length; i++ ) {
436 | var m = require( matches[i] );
437 | that.registerPlugin( m );
438 | }
439 |
440 | // ****************************************************************************************
441 | // Internal Helper
442 | // ****************************************************************************************
443 | var _getHeaders = function ( additionalHeaders ) {
444 |
445 | var header = {
446 | 'X-Qlik-xrfkey': that.config.xrfkey
447 | };
448 | if ( that.config.headerKey && that.config.headerValue ) {
449 | header[that.config.headerKey] = that.config.headerValue;
450 | }
451 | _.extend( header, additionalHeaders || {} );
452 | return header;
453 |
454 | };
455 |
456 | // ****************************************************************************************
457 | // Validation
458 | //Todo: implement a more generic validation
459 | // ****************************************************************************************
460 | var _validateConfig = function () {
461 |
462 | var required = [];
463 | switch ( that.config.authentication ) {
464 | case 'header':
465 | required = ['headerKey', 'headerValue', 'xrfkey', 'useSSL', 'virtualProxy'];
466 | return _validateConfigMissing( that.config, required );
467 | case 'ntlm':
468 | return true;
469 | case 'certificates':
470 | required = ['cert', 'key', 'ca'];
471 | return _validateConfigMissing( that.config, required );
472 | default:
473 | return true;
474 | }
475 | };
476 |
477 | var _validateConfigMissing = function ( configs, required ) {
478 |
479 | var missing = [];
480 | _.each( required, function ( item ) {
481 | if ( !configs.hasOwnProperty( item ) ) {
482 | missing.push( item );
483 | }
484 | } );
485 | return (missing.length === 0);
486 | };
487 |
488 | };
489 | module.exports = qrs;
490 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/qrs) [](https://travis-ci.org/stefanwalther/qrs) [](https://david-dm.org/stefanwalther/qrs)
2 | # qrs
3 | > Node.js library to communicate with Qlik Sense Repository Service (QRS) API.
4 |
5 | NOTE: This solution is not actively maintained anymore. Contributors to take over are highly welcome.
6 | Alternatively use [qrs-interact](https://github.com/eapowertools/qrs-interact)
7 |
8 | 
9 |
10 | ## Installation
11 | Install with [npm](https://www.npmjs.com/):
12 |
13 | ```sh
14 | $ npm install --save qrs
15 | ```
16 |
17 | ---
18 | ## Table of Contents
19 | - [Usage](#usage)
20 | - [Configuration Options](#configuration-options)
21 | - [Server Setup](#server-setup)
22 | - [API](#api)
23 | - [Plugins](#plugins)
24 | - [Running tests](#running-tests)
25 | - [Contributing](#contributing)
26 | - [Author](#author)
27 | - [License](#license)
28 |
29 | ---
30 |
31 | ## Usage
32 | ```js
33 | var QRS = require('qrs');
34 | var config = {
35 | "host": 'qsSingle',
36 | "useSSL": false,
37 | "xrfkey": 'ABCDEFG123456789',
38 | "authentication": "header",
39 | "headerKey": 'hdr-usr',
40 | "headerValue": 'qsSingle\\swr'
41 | };
42 | var qrs = new QRS( config );
43 |
44 | // Now run your command like
45 | qrs.get('qrs/about', function( data ) {
46 |
47 | // do something with the result
48 |
49 | });
50 | ```
51 |
52 | ## Configuration Options
53 | The configuration passed to the constructor of *qrs* drives how authentication is handled.
54 |
55 | ### Typical configurations
56 |
57 | **Example using header authentication**
58 |
59 | ```javascript
60 | var config = {
61 | authentication: 'header',
62 | host: 'server.mydomain.com',
63 | useSSL: true,
64 | virtualProxy: 'hdr',
65 | headerKey: 'hdr-usr',
66 | headerValue: 'mydomain\\justme'
67 | };
68 | ```
69 |
70 | **Example using certificates**
71 |
72 | ```js
73 | var config = {
74 | authentication: 'certificates',
75 | host: 'server.mydomain.com',
76 | useSSL: true,
77 | cert: 'C:\\CertStore\\client.pem',
78 | key: 'C:\\CertStore\\client_key.pem',
79 | ca: 'C:\\CertStore\\root.pem',
80 | port: 4242,
81 | headerKey: 'X-Qlik-User',
82 | headerValue: 'UserDirectory=Internal;UserId=sa_repository'
83 | };
84 | ```
85 |
86 | ### All options
87 |
88 | * **`authentication`** - Authentication method, can be "`windows`", "`certificates`" or "`header`", defaults to "`windows`".
89 | * **`host`** - Qualified / fully qualified name or IP-address of the server where the Qlik Sense Repository server is running on, defaults to "`127.0.0.1`"
90 | * **`useSSL`** - Whether to use SSL or not, defaults to `false`.
91 | * **`headerKey`** - Header key.
92 | * **`headerValue`** - Header value.
93 | * **`virtualProxy`** - Name of the virtual proxy.
94 | * **`port`** - Port to be used.
95 | * **`cert`** - Path to client certificate file (client.pem).
96 | * **`key`** - Path to client key file (client_key.pem).
97 | * **`ca`** - Path to ca file (root.pem)
98 |
99 | ## Server Setup
100 | There are several options to ensure that communication between this node.js module and Qlik Sense server is working properly:
101 |
102 | **Authenticating with HTTP headers**
103 | * [Using Header Authentication in Qlik Sense: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/header-authentication-configuration)
104 | * [Authenticating with HTTP headers](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Http-Headers.htm) in Qlik Sense Help for Developers 2.1.1
105 |
106 | **Authenticating with a server certificate**
107 | * [Authenticating with Certificates: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/authentication-certificates)
108 | * [Authenticating with the server certificate](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Certificate.htm) in Qlik Sense Help for Developer 2.1.1
109 |
110 | ## API
111 |
112 | ### [qrs](lib/qrs.js#L37)
113 | Work with Qlik Sense's REST based Repository API (qrs) from within node.js.
114 |
115 | **Configuration options:**
116 |
117 | **Params**
118 |
119 | * `qrsConfig` **{Object}**: Global configuration options
120 |
121 | **Example**
122 |
123 | ```js
124 | var QRS = require('qrs');
125 | var config = {
126 |
127 | var qrs = new QRS( config );
128 |
129 | ```
130 |
131 | ### [.request](lib/qrs.js#L82)
132 | (Internal) generic method to send requests to QRS. Typically this method is only used internally, use `get`, `post`, `put` or `delete`.
133 |
134 | **Params**
135 |
136 | * `method` **{String}**: Http method, can be `GET`, `POST`, `PUT` or `DELETE` (defaults to `GET`).
137 | * `endpoint` **{String}**: Endpoint to be used. Check the online documentation of the Qlik Sense Repository API to get a list of all endpoints available.
138 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`.
139 | * `jsonBody` **{Object}**: JSON object to be used as the body for the Http request.
140 | * `body` **{String}**: Body, if not defined as Json object, body needs to be passed as a buffer to e.g. upload a file.
141 | * `additionalRequestOptions` **{Object}**: Additional request options.
142 | * `additionalHeaders` **{Object}**: Additional headers.
143 | * `returns` **{promise}**: Returns a promise.
144 |
145 | **Example**
146 |
147 | ```js
148 | var QRS = require('qrs');
149 |
150 | var qrsConfig = ...; // Set configuration options
151 | var qrs = new QRS( qrsConfig );
152 |
153 | qrs.request( 'GET', 'qrs/about', null, null)
154 | .then( function( data ) {
155 | console.log( 'about', data );
156 | }, function ( err ) {
157 | console.error( 'An error occurred: ', err);
158 | });
159 | ```
160 |
161 | ### [.get](lib/qrs.js#L173)
162 | Same as `request()` but with `method: 'GET'`.
163 |
164 | **Params**
165 |
166 | * `endpoint` **{String}**: QRS endpoint to be used.
167 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`.
168 | * `returns` **{promise}**: Returns a promise.
169 |
170 | **Example**
171 |
172 | ```js
173 | qrs.get( 'qrs/about')
174 | .then( function ( data) {
175 | console.log('about: ', data );
176 | }, function ( err ) {
177 | console.error( err );
178 | });
179 | ```
180 |
181 | ### [.post](lib/qrs.js#L186)
182 |
183 | Same as `request()` but with `method: 'POST'`.
184 |
185 | **Params**
186 |
187 | * `endpoint` **{String}**: QRS endpoint to be used.
188 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`.
189 | * `jsonBody` **{Object}**: Body to be posted, defined as JSON object.
190 | * `returns` **{promise}**: Returns a promise.
191 |
192 | ### [.postFile](lib/qrs.js#L199)
193 |
194 | Post a file, actually same as `post()`, instead of posting a JSON body, posts a file buffer.
195 |
196 | **Params**
197 |
198 | * `endpoint` **{String}**: QRS endpoint to be used.
199 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`
200 | * **{String}**: filePath Absolute or relative file path.
201 | * `returns` **{promise}**: Returns a promise.
202 |
203 | ### [.put](lib/qrs.js#L215)
204 |
205 | Same as `request()` but with `method: 'PUT'`.
206 |
207 | ### [.delete](lib/qrs.js#L225)
208 |
209 | Same as `request()` but with `method: 'DELETE'`.
210 |
211 | ### [.getUrl](lib/qrs.js#L240)
212 |
213 | Return the Url for the REST call considering the given configuration options
214 |
215 | **Params**
216 |
217 | * `endpoint` **{string}**: Endpoint for the qrs call.
218 | * `urlParams` **{Object[]}**: Additional URL parameters as key/value array.
219 | * `urlParam.key` **{String}**: Key.
220 | * `urlParam.value` **{Object}**: Value.
221 | * `returns` **{String}**: The constructed Url.
222 |
223 | ### [.setConfig](lib/qrs.js#L297)
224 | Set global configurations options for qrs. Can be used to change the configuration options after `qrs` has been initialized.
225 |
226 | **Params**
227 |
228 | * `qrsConfig` **{Object}**: Global configuration options
229 |
230 | **Example**
231 |
232 | ```js
233 | var QRS = require('qrs');
234 | var configCert = {
235 | authentication: 'certificates',
236 | ...
237 | };
238 | var qrs = new QRS( configCert );
239 |
240 | // Talking to the server using certificates
241 | qrs.get('qrs/about', function( result ) {
242 | // ...
243 | });
244 |
245 | // Change configuration options, e.g.
246 | var configHeader = {
247 | authentication: 'header',
248 | ...
249 | };
250 |
251 | qrs.setConfig( configHeader);
252 |
253 | // Talking to the server, now using header authentication.
254 | qrs.get('qrs/about', function( result ) {
255 | // ...
256 | });
257 | ```
258 |
259 | ### [.getConfig](lib/qrs.js#L320)
260 | Return the current configuration options.
261 |
262 | * `returns` **{qrsConfig}** `qrsConfig`: Configuration object.
263 |
264 | **Example**
265 |
266 | ```js
267 | var QRS = require('qrs');
268 | var config = {
269 | host: 'myserver.domain.com';
270 | };
271 | var qrs = new QRS( config );
272 | var host = qrs.getConfig( 'host' );
273 | console.log(host); //<== 'myserver.domain.com'
274 | ```
275 |
276 | ### [.set](lib/qrs.js#L348)
277 | Change a single configuration property.
278 |
279 | **Params**
280 |
281 | * `key` **{String}**: Key of the property
282 | * `val` **{Object}**: Value
283 |
284 | **Example**
285 |
286 | ```js
287 | var QRS = require('qrs');
288 | var config = {
289 | host: 'myserver.domain.com';
290 | };
291 | var qrs = new QRS( config );
292 |
293 | qrs.get('qrs/about', function( result ) {
294 | // about from myserver.domain.com
295 | });
296 |
297 | qrs.set('host', 'mysecondserver.domain.com');
298 | qrs.get('qrs/about', function( result ) {
299 | // about from mysecondserver.domain.com
300 | });
301 | ```
302 |
303 | ### [.getConfigValue](lib/qrs.js#L359)
304 |
305 | Retrieve a single configuration property.
306 |
307 | **Params**
308 |
309 | * `key` **{String}**: Key of the property
310 | * `returns` **{Object}**: Value of the requested property, otherwise undefined.
311 |
312 | ### [.plugins](lib/qrs.js#L373)
313 |
314 | Returns an array of loaded plugins. Use `registerPlugin()` to load a plugin.
315 |
316 | ### [.registerPlugin](lib/qrs.js#L423)
317 | Register a plugin to work with the base class of `qrs`. Have a look at some of the already existing plugins like `./lib/sugar/ep-mime.js`
318 |
319 | **Params**
320 |
321 | * **{Object}**: plugin
322 |
323 | **Example**
324 |
325 | ```js
326 |
327 | // -----------------------------------------------------------
328 | // Define the plugin.
329 | // ~~
330 | // Purpose: Do something great with extensions.
331 | // Filename: ep-extension.js
332 | // -----------------------------------------------------------
333 |
334 | function Extension ( base ) {
335 |
336 | function doSomething() {
337 | base.get( 'qrs/extension/schema')
338 | .then( function( result ) {
339 | // result now holding the result from the server
340 | }, function (err) {
341 | // error handling
342 | });
343 |
344 | return {
345 | doSomething: doSomething
346 | };
347 | }
348 |
349 | module.exports = Extension;
350 |
351 | // -----------------------------------------------------------
352 | // Register and use it
353 | // -----------------------------------------------------------
354 |
355 | var qrs = new QRS( config );
356 | qrs.registerPlugin('./ep-extension.js');
357 |
358 | // Use the plugin
359 | qrs.extension.upload( myFile, function( result ) {
360 | // The file has been uploaded
361 | });
362 |
363 | ```
364 |
365 | ## Plugins
366 | Think of plugins as some kind of sugar layer with additional business logic on top of `qrs`.
367 | It is easy to either add new plugins to the `qrs` repository or just to load some external code/plugin.
368 | The list of built-in plugins is probably and hopefully a growing one (and hopefully not only created by the author of this document).
369 |
370 | The following plugins are available with the current version of `qrs`:
371 |
372 |
373 |
374 |
375 |
376 | ### Plugin "Mime"
377 |
378 | Mime type definition
379 |
380 | ### [Mime](lib/sugar/ep-mime.js#L24)
381 |
382 | Handle Mime types in QRS.
383 |
384 | **Params**
385 |
386 | * **{qrs}**: base - Base class, instance of `qrs`.
387 |
388 | ### [add](lib/sugar/ep-mime.js#L91)
389 | Adds a mime type.
390 |
391 | When adding the mime type, the following validation logic will be performed:
392 | - All existing mime types will be grouped by mime, additionalHeaders and binary.
393 | - If there is already a mime type with the same information compared to the given one, the field extension will be updated.
394 | An example is listed below.
395 |
396 | **Params**
397 |
398 | * **{Object}**: mimeTypeDef
399 | * **{string}**: mimeTypeDef.mime - Mime type, e.g. "application/markdown".
400 | * **{string}**: mimeTypeDef.extensions - Comma delimited string of supported file extensions, e.g. "md,markdown".
401 | * **{boolean}**: mimeTypeDef.additionalHeaders - Additional headers, defaults to null.
402 | * **{boolean}**: mimeTypeDef.binary - Whether this is a binary file type or not.
403 | * `returns` **{promise}**
404 |
405 | **Example**
406 |
407 | ```js
408 | // -----------------------------------------------------------------
409 | // Inserting logic
410 | // ----------------------------------------------------------------- *
411 |
412 | // Assuming that the following mime type is already stored:
413 | {
414 | extensions: "md"
415 | mime: "application/markdown",
416 | additionalHeaders: null,
417 | binary: false
418 | }
419 | // If you add the following mime type
420 | {
421 | extensions: "markdown"
422 | mime: "application/markdown",
423 | additionalHeaders: null,
424 | binary: false
425 | }
426 | //this would then result into:
427 | {
428 | extensions: "md,markdown"
429 | mime: "application/markdown",
430 | additionalHeaders: null,
431 | binary: false
432 | }
433 |
434 | // -----------------------------------------------------------------
435 | // Adding a mime type:
436 | // -----------------------------------------------------------------
437 |
438 | var mimeType = {
439 | extensions: "md"
440 | mime: "application/markdown",
441 | additionalHeaders: null,
442 | binary: false
443 | }
444 |
445 | qrs.mime.add( mimeType )
446 | .then( function (result ) {
447 | // work with the result
448 | }, function (err) {
449 | // error handling
450 | });
451 |
452 | ```
453 |
454 | ### [get](lib/sugar/ep-mime.js#L144)
455 | Returns a list of existing mime types.
456 |
457 | The list can be filtered by the file-extensions as shown in the example.
458 |
459 | **Params**
460 |
461 | * **{string}**: filter
462 | * `returns` **{promise}**
463 |
464 | **Example**
465 |
466 | ```js
467 | getMimeTypes( 'html')
468 | .then( function (data) {
469 |
470 | // data now contains an array of mime types where the field extensions contains "html"
471 | // Results: html, xhtml, etc.
472 |
473 | })
474 | ```
475 |
476 | ### [addMultiple](lib/sugar/ep-mime.js#L161)
477 |
478 | Adds an array of mime types
479 | (See `add` for more information about `mimeTypeDef`).
480 |
481 | **Params**
482 |
483 | * **{mimeTypeDef[]}**: mimeTypeDefs - Array of mime type definitions.
484 | * `returns` **{promise}**
485 |
486 | ### [addFromFile](lib/sugar/ep-mime.js#L206)
487 | Add mime types defined in a file. Every line in the file is defined by the following entries, delimited by a semi-colon (;): - extensions - {string} file extension, multiple values separated by a comma, e.g. "md,markdown" - mime - {string} Mime type - additionalHeaders - {boolean} Additional defined headers, leave blank if unsure - binary - {boolean} Whether this is a binary format or not.
488 |
489 | **Params**
490 |
491 | * **{String}**: filePath - Absolute file path.
492 | * `returns` **{promise}**
493 |
494 | **Example**
495 |
496 | ```bash
497 | md;application/markdown;;false
498 | yml;text/yml;;false
499 | woff2;application/font-woff2;;true
500 | ```
501 |
502 | ### [deleteById](lib/sugar/ep-mime.js#L240)
503 |
504 | Delete a mime entry from the Qlik Sense Repository by its given Id.
505 |
506 | **Params**
507 |
508 | * **{String}**: id
509 | * `returns` **{promise}**
510 |
511 | ### [getUpdateOrInsert](lib/sugar/ep-mime.js#L308)
512 |
513 | Returns whether the mime type already exists or not.
514 |
515 | **Params**
516 |
517 | * **{mimeTypeDef}**: mimeTypeDef
518 | * `returns` **{object}**: result - Returned result.
519 | * `returns` **{boolean}**: result.isUpdate - Whether to update or add.
520 |
521 | ---
522 |
523 | ## Running tests
524 | Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
525 |
526 | ```sh
527 | $ npm install && npm test
528 | ```
529 |
530 | ## Contributing
531 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
532 |
533 | ## Author
534 | **Stefan Walther**
535 | * [qliksite.io](http://qliksite.io)
536 | * [twitter/waltherstefan](http://twitter.com/waltherstefan)
537 | * [github.com/stefanwalther](http://github.com/stefanwalther)
538 |
539 | ## License
540 | Copyright © 2018, Stefan Walther.
541 | MIT
542 |
543 | ***
544 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on January 21, 2018._
545 |
546 |
--------------------------------------------------------------------------------