Skip to content

Commit d9ca218

Browse files
committed
test: add get management token integration tests
1 parent 1029a85 commit d9ca218

16 files changed

+202
-75
lines changed

.env.tpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# App Definition for the test Application
2+
APP_DEFINITION_ID=
3+
# App Installation for the test Application
4+
APP_INSTALLATION_ID=
5+
# Space where the Application is Installed
6+
SPACE_ID=
7+
# Environment where the Application is Installed
8+
ENVIRONMENT_ID=
9+
# Organization where the Application Definition belongs
10+
ORGANIZATION_ID=
11+
# A personal access token to register the keypair before the tests
12+
PERSONAL_ACCESS_TOKEN=

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
node_modules
2-
.idea
2+
.idea
3+
keys
4+
.env

.typedocrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"exclude": [
77
"**/*.spec.ts",
88
"**/*/index.ts",
9-
"src/utils/**/*"
9+
"src/utils/**/*",
10+
"test/**/*"
1011
],
1112
"out": "docs",
1213
"stripInternal": true,

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,22 @@ and include it in your code like
1515

1616
```js
1717
const {getManagementToken} = require('contentful-node-apps-toolkit');
18+
const {appInstallationId, spaceId, privateKey} = require('./some-constants');
1819

19-
getManagementToken(PRIVATE_KEY)
20-
.then(token => {
20+
getManagementToken(privateKey, {appInstallationId, spaceId})
21+
.then((token) => {
2122
console.log('Here is your app token')
2223
console.log(token)
2324
})
2425
```
2526

2627
## API Docs
2728

28-
API documentation is available [here](https://contentful.github.io/node-apps-toolkit/)
29+
API documentation is available [here](https://contentful.github.io/node-apps-toolkit/)
30+
31+
## Testing
32+
33+
> **:warning: Please Note**
34+
>
35+
> In order to run integration tests all the environment variables present in
36+
[`.env.tpl`](./.env.tpl) must be provided.

docs/index.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,25 @@ <h2>Getting started</h2>
7171
<pre><code>npm install --save contentful-<span class="hljs-keyword">node</span><span class="hljs-title">-apps-toolkit</span></code></pre>
7272
<p>and include it in your code like</p>
7373
<pre><code class="language-js"><span class="hljs-keyword">const</span> {getManagementToken} = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;contentful-node-apps-toolkit&#x27;</span>);
74+
<span class="hljs-keyword">const</span> {appInstallationId, spaceId, privateKey} = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./some-constants&#x27;</span>);
7475

75-
getManagementToken(PRIVATE_KEY)
76-
.then(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
76+
getManagementToken(privateKey, {appInstallationId, spaceId})
77+
.then(<span class="hljs-function">(<span class="hljs-params">token</span>) =&gt;</span> {
7778
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">&#x27;Here is your app token&#x27;</span>)
7879
<span class="hljs-built_in">console</span>.log(token)
7980
})</code></pre>
8081
<a href="#api-docs" id="api-docs" style="color: inherit; text-decoration: none;">
8182
<h2>API Docs</h2>
8283
</a>
8384
<p>API documentation is available <a href="https://contentful.github.io/node-apps-toolkit/">here</a></p>
85+
<a href="#testing" id="testing" style="color: inherit; text-decoration: none;">
86+
<h2>Testing</h2>
87+
</a>
88+
<blockquote>
89+
<p><strong>:warning: Please Note</strong></p>
90+
<p>In order to run integration tests all the environment variables present in
91+
<a href="./.env.tpl"><code>.env.tpl</code></a> must be provided.</p>
92+
</blockquote>
8493
</div>
8594
</div>
8695
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">

docs/modules/_get_management_token_.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ <h3><span class="tsd-flag ts-flagConst">Const</span> get<wbr>Management<wbr>Toke
8787
<li class="tsd-description">
8888
<aside class="tsd-sources">
8989
<ul>
90-
<li>Defined in <a href="https://github.com/contentful/node-apps-toolkit/blob/998610d/src/keys/get-management-token.ts#L87">get-management-token.ts:87</a></li>
90+
<li>Defined in <a href="https://github.com/contentful/node-apps-toolkit/blob/3b59fd3/src/keys/get-management-token.ts#L93">get-management-token.ts:93</a></li>
9191
</ul>
9292
</aside>
9393
<div class="tsd-comment tsd-typography">

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"scripts": {
77
"lint": "eslint --ext .ts ./src",
88
"lint:fix": "npm run lint -- --fix",
9-
"test": "mocha -r ts-node/register ./src/**/*.spec.ts",
9+
"pretest": "echo ' 🔑 Creating valid keypair for testing' && sh test/make-private-keys.sh &> /dev/null",
10+
"test:unit": "mocha -r dotenv/config -r ts-node/register ./src/**/*.spec.ts",
11+
"test:integration": "mocha -r dotenv/config -r ts-node/register ./test/**/*.test.ts",
12+
"test": "npm run test:unit && npm run test:integration",
1013
"build": "npm run build:lib && npm run build:docs",
1114
"build:lib": "rm -rf lib && tsc",
1215
"build:docs": "typedoc --options .typedocrc.json src",
@@ -28,7 +31,9 @@
2831
"@types/sinon": "^9.0.7",
2932
"@typescript-eslint/eslint-plugin": "^4.3.0",
3033
"@typescript-eslint/parser": "^4.3.0",
34+
"base64url": "^3.0.1",
3135
"debug": "^4.2.0",
36+
"dotenv": "^8.2.0",
3237
"eslint": "^7.10.0",
3338
"eslint-plugin-prettier": "^3.1.4",
3439
"husky": "^4.3.0",

src/keys/__fixtures__/key.der.pub

-550 Bytes
Binary file not shown.

src/keys/__fixtures__/key.pem

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/keys/get-management-token.spec.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { createGetManagementToken, getManagementToken } from './get-management-t
88
import { HttpClient, HttpError, Response } from '../utils'
99
import { Logger } from '../utils'
1010

11-
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, '__fixtures__', 'key.pem'), 'utf-8')
11+
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, '..', '..', 'keys', 'key.pem'), 'utf-8')
1212
const APP_ID = 'app_id'
1313
const SPACE_ID = 'space_id'
1414
const ENVIRONMENT_ID = 'env_id'
15-
const DEFAULT_OPTIONS = { appId: APP_ID, spaceId: SPACE_ID, environmentId: ENVIRONMENT_ID }
15+
const DEFAULT_OPTIONS = {
16+
appInstallationId: APP_ID,
17+
spaceId: SPACE_ID,
18+
environmentId: ENVIRONMENT_ID
19+
}
1620
const noop = () => {}
1721

1822
describe('getManagementToken', () => {
@@ -35,6 +39,27 @@ describe('getManagementToken', () => {
3539
)
3640
})
3741

42+
describe('when using a keyId', () => {
43+
it('fetches a token', async () => {
44+
const mockToken = 'token'
45+
const logger = (noop as unknown) as Logger
46+
const post = sinon.stub()
47+
post.resolves({ statusCode: 201, body: JSON.stringify({ token: mockToken }) })
48+
const httpClient = ({ post } as unknown) as HttpClient
49+
const getManagementToken = createGetManagementToken(logger, httpClient)
50+
51+
const result = await getManagementToken(PRIVATE_KEY, { ...DEFAULT_OPTIONS, keyId: 'keyId' })
52+
53+
assert.deepStrictEqual(result, mockToken)
54+
assert(
55+
post.calledWith(
56+
`spaces/${SPACE_ID}/environments/${ENVIRONMENT_ID}/app_installations/${APP_ID}/access_tokens`,
57+
sinon.match({ headers: { Authorization: sinon.match.string } })
58+
)
59+
)
60+
})
61+
})
62+
3863
describe('when private key is incorrect', () => {
3964
it('throws if missing', async () => {
4065
await assert.rejects(async () => {

src/keys/get-management-token.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { sign } from 'jsonwebtoken'
1+
import { sign, SignOptions } from 'jsonwebtoken'
22

33
import {
44
createLogger,
@@ -9,18 +9,26 @@ import {
99
} from '../utils'
1010

1111
interface GetManagementTokenOptions {
12-
appId: string
12+
appInstallationId: string
1313
spaceId: string
14-
environmentId?: string
14+
environmentId: string
15+
keyId?: string
1516
}
1617

1718
/**
1819
* Synchronously sign the given privateKey into a JSON Web Token string
1920
*/
20-
const generateAppToken = (privateKey: string, appId: string, { log }: { log: Logger }): string => {
21+
const generateOneTimeToken = (
22+
privateKey: string,
23+
{ appId, keyId }: { appId: string; keyId?: string },
24+
{ log }: { log: Logger }
25+
): string => {
2126
log('Signing a JWT token with private key')
2227
try {
23-
const token = sign({}, privateKey, { algorithm: 'RS256', issuer: appId, expiresIn: '10m' })
28+
const baseSignOptions: SignOptions = { algorithm: 'RS256', issuer: appId, expiresIn: '10m' }
29+
const signOptions: SignOptions = keyId ? { ...baseSignOptions, keyid: keyId } : baseSignOptions
30+
31+
const token = sign({}, privateKey, signOptions)
2432
log('Successfully signed token')
2533
return token
2634
} catch (e) {
@@ -29,16 +37,21 @@ const generateAppToken = (privateKey: string, appId: string, { log }: { log: Log
2937
}
3038
}
3139

32-
const getTokenFromAppToken = async (
40+
const getTokenFromOneTimeToken = async (
3341
appToken: string,
34-
{ appId, spaceId, environmentId }: { appId: string; spaceId: string; environmentId?: string },
42+
{
43+
appInstallationId,
44+
spaceId,
45+
environmentId
46+
}: { appInstallationId: string; spaceId: string; environmentId: string },
3547
{ log, http }: { log: Logger; http: HttpClient }
3648
) => {
3749
const validateStatusCode = createValidateStatusCode([201])
3850

51+
log(`Requesting CMA Token with given App Token`)
52+
3953
const response = await http.post(
40-
`spaces/${spaceId}/environments/${environmentId ??
41-
'master'}/app_installations/${appId}/access_tokens`,
54+
`spaces/${spaceId}/environments/${environmentId}/app_installations/${appInstallationId}/access_tokens`,
4255
{
4356
headers: {
4457
Authorization: `Bearer ${appToken}`
@@ -50,7 +63,7 @@ const getTokenFromAppToken = async (
5063
)
5164

5265
log(
53-
`Successfully retrieved app access token for app ${appId} in space ${spaceId} and environment ${environmentId}`
66+
`Successfully retrieved CMA Token for app ${appInstallationId} in space ${spaceId} and environment ${environmentId}`
5467
)
5568

5669
return JSON.parse(response.body).token
@@ -66,8 +79,12 @@ export const createGetManagementToken = (log: Logger, http: HttpClient) => {
6679
throw new ReferenceError('Invalid privateKey: expected a string representing a private key')
6780
}
6881

69-
const appToken = generateAppToken(privateKey, opts.appId, { log })
70-
return getTokenFromAppToken(appToken, opts, { log, http })
82+
const appToken = generateOneTimeToken(
83+
privateKey,
84+
{ appId: opts.appInstallationId, keyId: opts.keyId },
85+
{ log }
86+
)
87+
return getTokenFromOneTimeToken(appToken, opts, { log, http })
7188
}
7289
}
7390

src/utils/http.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const createHttpClient = () => {
1111

1212
export const createValidateStatusCode = (allowedStatusCodes: number[]) => (response: Response) => {
1313
if (!allowedStatusCodes.includes(response.statusCode)) {
14+
console.log(response.body)
1415
throw new HTTPError(response)
1516
}
1617
return response

test/integration/keys.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as fs from 'fs'
2+
import * as path from 'path'
3+
import * as assert from 'assert'
4+
5+
//TODO use built version
6+
import { getManagementToken } from '../../src/keys'
7+
import { setPublicKey } from '../utils'
8+
import { createHttpClient } from '../../src/utils'
9+
10+
describe('Keys Utilities', () => {
11+
before(async () => {
12+
const pubKey = fs.readFileSync(path.join(__dirname, '..', '..', 'keys', 'key.der.pub'))
13+
14+
await setPublicKey(pubKey)
15+
})
16+
17+
it('fetches a valid CMA token for a valid key pair', async () => {
18+
const appInstallationId = process.env.APP_INSTALLATION_ID
19+
const spaceId = process.env.SPACE_ID
20+
const environmentId = process.env.ENVIRONMENT_ID
21+
22+
if (!appInstallationId || !spaceId || !environmentId) {
23+
throw new Error('Missing Environment setup')
24+
}
25+
26+
const privateKey = fs.readFileSync(path.join(__dirname, '..', '..', 'keys', 'key.pem'), 'utf-8')
27+
28+
const token = await getManagementToken(privateKey, {
29+
appInstallationId,
30+
spaceId,
31+
environmentId
32+
})
33+
34+
await assert.doesNotReject(() => {
35+
const http = createHttpClient()
36+
37+
return http.get(`spaces/${spaceId}/entries`, {
38+
headers: {
39+
Authorization: `Bearer ${token}`
40+
}
41+
})
42+
})
43+
})
44+
})

test/make-private-keys.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env sh
2+
3+
mkdir -p keys
4+
5+
openssl req -x509 -newkey rsa:4096 -nodes -batch -noout -keyout keys/key.pem
6+
openssl rsa -in keys/key.pem -pubout -outform DER -out keys/key.der.pub

0 commit comments

Comments
 (0)