Pārlūkot izejas kodu

API Integration Tests via jest (#10899)

* tests: experiment with api tests

* api tests are getting nice

* api: api testing ready for feedback
Torkel Ödegaard 7 gadi atpakaļ
vecāks
revīzija
ead1c300d7

+ 1 - 1
.jshintrc

@@ -4,7 +4,7 @@
   "bitwise":false,
   "curly": true,
   "eqnull": true,
-  "strict": true,
+  "strict": false,
   "devel": true,
   "eqeqeq": true,
   "forin": false,

+ 2 - 0
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": {

+ 7 - 0
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();
+  });
+});

+ 30 - 0
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;

+ 45 - 0
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);
+    });
+  });
+});

+ 19 - 0
tests/api/jest.js

@@ -0,0 +1,19 @@
+module.exports = {
+  verbose: true,
+  "globals": {
+    "ts-jest": {
+      "tsConfigFile": "tsconfig.json"
+    }
+  },
+  "transform": {
+    "^.+\\.tsx?$": "<rootDir>/../../node_modules/ts-jest/preprocessor.js"
+  },
+  "moduleDirectories": ["node_modules"],
+  "testRegex": "(\\.|/)(test)\\.ts$",
+  "testEnvironment": "node",
+  "moduleFileExtensions": [
+    "ts",
+    "js",
+    "json"
+  ],
+};

+ 27 - 0
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);
+    });
+  });
+});

+ 107 - 0
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;
+}

+ 24 - 0
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"
+    ]
+}

+ 22 - 0
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');
+  });
+});

+ 89 - 2
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"