From ead1c300d73a1a6249aab5084b5498b3c95bc917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 14 Feb 2018 10:26:20 +0100 Subject: [PATCH] API Integration Tests via jest (#10899) * tests: experiment with api tests * api tests are getting nice * api: api testing ready for feedback --- .jshintrc | 2 +- package.json | 2 + tests/api/clearState.test.ts | 7 +++ tests/api/client.ts | 30 ++++++++++ tests/api/dashboard.test.ts | 45 +++++++++++++++ tests/api/jest.js | 19 +++++++ tests/api/search.test.ts | 27 +++++++++ tests/api/setup.ts | 107 +++++++++++++++++++++++++++++++++++ tests/api/tsconfig.json | 24 ++++++++ tests/api/user.test.ts | 22 +++++++ yarn.lock | 91 ++++++++++++++++++++++++++++- 11 files changed, 373 insertions(+), 3 deletions(-) create mode 100644 tests/api/clearState.test.ts create mode 100644 tests/api/client.ts create mode 100644 tests/api/dashboard.test.ts create mode 100644 tests/api/jest.js create mode 100644 tests/api/search.test.ts create mode 100644 tests/api/setup.ts create mode 100644 tests/api/tsconfig.json create mode 100644 tests/api/user.test.ts diff --git a/.jshintrc b/.jshintrc index 3fb6501c2f9..1d8fad63173 100644 --- a/.jshintrc +++ b/.jshintrc @@ -4,7 +4,7 @@ "bitwise":false, "curly": true, "eqnull": true, - "strict": true, + "strict": false, "devel": true, "eqeqeq": true, "forin": false, diff --git a/package.json b/package.json index aad7d2ce92c..07014ef47d4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "angular-mocks": "^1.6.6", "autoprefixer": "^6.4.0", "awesome-typescript-loader": "^3.2.3", + "axios": "^0.17.1", "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-es2015": "^6.24.1", @@ -105,6 +106,7 @@ "lint": "tslint -c tslint.json --project tsconfig.json --type-check", "karma": "node ./node_modules/grunt-cli/bin/grunt karma:dev", "jest": "node ./node_modules/jest-cli/bin/jest.js --notify --watch", + "api-tests": "node ./node_modules/jest-cli/bin/jest.js --notify --watch --config=tests/api/jest.js", "precommit": "lint-staged && node ./node_modules/grunt-cli/bin/grunt precommit" }, "lint-staged": { diff --git a/tests/api/clearState.test.ts b/tests/api/clearState.test.ts new file mode 100644 index 00000000000..8c5e5c1422b --- /dev/null +++ b/tests/api/clearState.test.ts @@ -0,0 +1,7 @@ +import * as setup from './setup'; + +describe.skip('clear state', () => { + it('will clear state', () => { + return setup.clearState(); + }); +}); diff --git a/tests/api/client.ts b/tests/api/client.ts new file mode 100644 index 00000000000..2e2f9ed67fd --- /dev/null +++ b/tests/api/client.ts @@ -0,0 +1,30 @@ +const axios = require('axios'); + +export function getClient(options) { + return axios.create({ + baseURL: 'http://localhost:3000', + timeout: 1000, + auth: { + username: options.username, + password: options.password, + }, + }); +} + +export function getAdminClient() { + return getClient({ + username: 'admin', + password: 'admin', + }); +} + +let client = getAdminClient(); + +client.callAs = function(user) { + return getClient({ + username: user.login, + password: 'password', + }); +}; + +export default client; diff --git a/tests/api/dashboard.test.ts b/tests/api/dashboard.test.ts new file mode 100644 index 00000000000..55beaffecb0 --- /dev/null +++ b/tests/api/dashboard.test.ts @@ -0,0 +1,45 @@ +import client from './client'; +import * as setup from './setup'; + +describe('/api/dashboards', () => { + let state: any = {}; + + beforeAll(async () => { + state = await setup.ensureState({ + orgName: 'api-test-org', + users: [ + { user: setup.admin, role: 'Admin' }, + { user: setup.editor, role: 'Editor' }, + { user: setup.viewer, role: 'Viewer' }, + ], + admin: setup.admin, + dashboards: [ + { + title: 'aaa', + uid: 'aaa', + }, + { + title: 'bbb', + uid: 'bbb', + }, + ], + }); + }); + + describe('With admin user', () => { + it('can delete dashboard', async () => { + let rsp = await client.callAs(setup.admin).delete(`/api/dashboards/uid/aaa`); + expect(rsp.data.title).toBe('aaa'); + }); + }); + + describe('With viewer user', () => { + it('Cannot delete dashboard', async () => { + let rsp = await setup.expectError(() => { + return client.callAs(setup.viewer).delete(`/api/dashboards/uid/bbb`); + }); + + expect(rsp.response.status).toBe(403); + }); + }); +}); diff --git a/tests/api/jest.js b/tests/api/jest.js new file mode 100644 index 00000000000..b32573115b7 --- /dev/null +++ b/tests/api/jest.js @@ -0,0 +1,19 @@ +module.exports = { + verbose: true, + "globals": { + "ts-jest": { + "tsConfigFile": "tsconfig.json" + } + }, + "transform": { + "^.+\\.tsx?$": "/../../node_modules/ts-jest/preprocessor.js" + }, + "moduleDirectories": ["node_modules"], + "testRegex": "(\\.|/)(test)\\.ts$", + "testEnvironment": "node", + "moduleFileExtensions": [ + "ts", + "js", + "json" + ], +}; diff --git a/tests/api/search.test.ts b/tests/api/search.test.ts new file mode 100644 index 00000000000..91d1ebf0d35 --- /dev/null +++ b/tests/api/search.test.ts @@ -0,0 +1,27 @@ +import client from './client'; +import * as setup from './setup'; + +describe('GET /api/search', () => { + const state = {}; + + beforeAll(async () => { + state = await setup.ensureState({ + orgName: 'api-test-org', + users: [{ user: setup.admin, role: 'Admin' }], + admin: setup.admin, + dashboards: [ + { + title: 'Dashboard in root no permissions', + uid: 'AAA', + }, + ], + }); + }); + + describe('With admin user', () => { + it('should return all dashboards', async () => { + let rsp = await client.callAs(state.admin).get('/api/search'); + expect(rsp.data).toHaveLength(1); + }); + }); +}); diff --git a/tests/api/setup.ts b/tests/api/setup.ts new file mode 100644 index 00000000000..0566729999c --- /dev/null +++ b/tests/api/setup.ts @@ -0,0 +1,107 @@ +import client from './client'; +import _ from 'lodash;'; + +export const editor = { + email: 'api-test-editor@grafana.com', + login: 'api-test-editor', + password: 'password', + name: 'Api Test Editor', +}; + +export const admin = { + email: 'api-test-admin@grafana.com', + login: 'api-test-admin', + password: 'password', + name: 'Api Test Super', +}; + +export const viewer = { + email: 'api-test-viewer@grafana.com', + login: 'api-test-viewer', + password: 'password', + name: 'Api Test Viewer', +}; + +export async function expectError(callback) { + try { + let rsp = await callback(); + return rsp; + } catch (err) { + return err; + } + + return rsp; +} + +// deletes org if it's already there +export async function getOrg(orgName) { + try { + const rsp = await client.get(`/api/orgs/name/${orgName}`); + await client.delete(`/api/orgs/${rsp.data.id}`); + } catch {} + + const rsp = await client.post(`/api/orgs`, { name: orgName }); + return { name: orgName, id: rsp.data.orgId }; +} + +export async function getUser(user) { + const search = await client.get('/api/users/search', { + params: { query: user.login }, + }); + + if (search.data.totalCount === 1) { + user.id = search.data.users[0].id; + return user; + } + + const rsp = await client.post('/api/admin/users', user); + user.id = rsp.data.id; + + return user; +} + +export async function addUserToOrg(org, user, role) { + const rsp = await client.post(`/api/orgs/${org.id}/users`, { + loginOrEmail: user.login, + role: role, + }); + + return rsp.data; +} + +export async function clearState() { + const admin = await getUser(adminUser); + const rsp = await client.delete(`/api/admin/users/${admin.id}`); + return rsp.data; +} + +export async function setUsingOrg(user, org) { + await client.callAs(user).post(`/api/user/using/${org.id}`); +} + +export async function createDashboard(user, dashboard) { + const rsp = await client.callAs(user).post(`/api/dashboards/db`, { + dashboard: dashboard, + overwrite: true, + }); + dashboard.id = rsp.data.id; + dashboard.url = rsp.data.url; + + return dashboard; +} + +export async function ensureState(state) { + const org = await getOrg(state.orgName); + + for (let orgUser of state.users) { + const user = await getUser(orgUser.user); + await addUserToOrg(org, user, orgUser.role); + await setUsingOrg(user, org); + } + + for (let dashboard of state.dashboards) { + await createDashboard(state.admin, dashboard); + } + + return state; +} diff --git a/tests/api/tsconfig.json b/tests/api/tsconfig.json new file mode 100644 index 00000000000..3dd8c94d7d0 --- /dev/null +++ b/tests/api/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "es6", + "lib": ["es6"], + "module": "commonjs", + "declaration": false, + "allowSyntheticDefaultImports": true, + "inlineSourceMap": false, + "sourceMap": true, + "noEmitOnError": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": true, + "noImplicitReturns": true, + "noImplicitThis": false, + "noImplicitUseStrict":false, + "noImplicitAny": false, + "noUnusedLocals": true + }, + "include": [ + "*.ts", + "**/*.ts" + ] +} diff --git a/tests/api/user.test.ts b/tests/api/user.test.ts new file mode 100644 index 00000000000..ef1c927c69e --- /dev/null +++ b/tests/api/user.test.ts @@ -0,0 +1,22 @@ +import client from './client'; +import * as setup from './setup'; + +describe('GET /api/user', () => { + it('should return current authed user', async () => { + let rsp = await client.get('/api/user'); + expect(rsp.data.login).toBe('admin'); + }); +}); + +describe('PUT /api/user', () => { + it('should update current authed user', async () => { + const user = await setup.getUser(setup.editor); + user.name = 'Updated via test'; + + const rsp = await client.callAs(user).put('/api/user', user); + expect(rsp.data.message).toBe('User updated'); + + const updated = await client.callAs(user).get('/api/user'); + expect(updated.data.name).toBe('Updated via test'); + }); +}); diff --git a/yarn.lock b/yarn.lock index e95968736ba..83a906c919d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -224,6 +224,14 @@ version "16.0.25" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.25.tgz#bf696b83fe480c5e0eff4335ee39ebc95884a1ed" +"@types/strip-bom@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + +"@types/strip-json-comments@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + JSONStream@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" @@ -699,6 +707,13 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +axios@^0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d" + dependencies: + follow-redirects "^1.2.5" + is-buffer "^1.1.5" + babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -1626,6 +1641,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +chalk@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + dependencies: + ansi-styles "^3.2.0" + escape-string-regexp "^1.0.5" + supports-color "^5.2.0" + chalk@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" @@ -2776,7 +2799,7 @@ diff@^2.0.2: version "2.2.3" resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" -diff@^3.2.0: +diff@^3.1.0, diff@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" @@ -3748,6 +3771,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@^1.2.5: + version "1.4.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa" + dependencies: + debug "^3.1.0" + for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -4385,6 +4414,10 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" @@ -4513,6 +4546,12 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" +homedir-polyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + hooker@^0.2.3, hooker@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" @@ -6211,6 +6250,10 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" +make-error@^1.1.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.3.tgz#a97ae14ffd98b05f543e83ddc395e1b2b6e4cc6a" + make-fetch-happen@^2.4.13, make-fetch-happen@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-2.6.0.tgz#8474aa52198f6b1ae4f3094c04e8370d35ea8a38" @@ -7371,6 +7414,10 @@ parse-json@^3.0.0: dependencies: error-ex "^1.3.1" +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + parse5@^3.0.1, parse5@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" @@ -9601,7 +9648,7 @@ strip-json-comments@1.0.x, strip-json-comments@~1.0.1, strip-json-comments@~1.0. version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" -strip-json-comments@~2.0.1: +strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -9633,6 +9680,12 @@ supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: dependencies: has-flag "^2.0.0" +supports-color@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" + dependencies: + has-flag "^3.0.0" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -9941,6 +9994,30 @@ ts-loader@^3.2.0: loader-utils "^1.0.2" semver "^5.0.1" +ts-node@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-4.1.0.tgz#36d9529c7b90bb993306c408cd07f7743de20712" + dependencies: + arrify "^1.0.0" + chalk "^2.3.0" + diff "^3.1.0" + make-error "^1.1.1" + minimist "^1.2.0" + mkdirp "^0.5.1" + source-map-support "^0.5.0" + tsconfig "^7.0.0" + v8flags "^3.0.0" + yn "^2.0.0" + +tsconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + dependencies: + "@types/strip-bom" "^3.0.0" + "@types/strip-json-comments" "0.0.30" + strip-bom "^3.0.0" + strip-json-comments "^2.0.0" + tslib@^1.7.1: version "1.8.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" @@ -10302,6 +10379,12 @@ uuid@^3.0.0, uuid@^3.1.0, uuid@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +v8flags@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.1.tgz#dce8fc379c17d9f2c9e9ed78d89ce00052b1b76b" + dependencies: + homedir-polyfill "^1.0.1" + validate-npm-package-license@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" @@ -10766,6 +10849,10 @@ yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" +yn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" + zip-stream@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04"