Skip to content

Commit 4744444

Browse files
authored
Merge pull request #297 from contentstack/feat/DX-2264-oauth-test-cases
feat: oauth test cases
2 parents 2f411ae + b310d86 commit 4744444

File tree

6 files changed

+482
-5
lines changed

6 files changed

+482
-5
lines changed

lib/core/contentstackHTTPClient.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ export default function contentstackHttpClient (options) {
6868
let uiHostName = hostname
6969
let developerHubBaseUrl = hostname
7070

71-
if (hostname.endsWith('io')) {
72-
uiHostName = hostname.replace('io', 'com')
71+
if (uiHostName?.endsWith('io')) {
72+
uiHostName = uiHostName.replace('io', 'com')
7373
}
7474

75-
if (hostname.startsWith('api')) {
75+
if (uiHostName?.startsWith('api')) {
7676
uiHostName = uiHostName.replace('api', 'app')
7777
}
7878
const uiBaseUrl = config.endpoint || `${protocol}://${uiHostName}`

lib/core/oauthHandler.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export default class OAuthHandler {
133133

134134
this.axiosInstance.defaults.headers = this._getHeaders()
135135
try {
136-
const response = await this.axiosInstance.post(`${this.OAuthBaseURL}/apps-api/apps/token`, body)
136+
const response = await this.axiosInstance.post(`${this.developerHubBaseUrl}/token`, body)
137137

138138
this._saveTokens(response.data)
139139
return response.data
@@ -180,7 +180,7 @@ export default class OAuthHandler {
180180

181181
this.axiosInstance.defaults.headers = this._getHeaders()
182182
try {
183-
const response = await this.axiosInstance.post(`${this.developerHubBaseUrl}/apps/token`, body)
183+
const response = await this.axiosInstance.post(`${this.developerHubBaseUrl}/token`, body)
184184

185185
const data = response.data
186186
this.axiosInstance.oauth.accessToken = data.access_token

test/sanity-check/api/oauth-test.js

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { expect } from 'chai'
2+
import { describe, it } from 'mocha'
3+
import { contentstackClient } from '../../sanity-check/utility/ContentstackClient'
4+
import axios from 'axios'
5+
import dotenv from 'dotenv'
6+
7+
dotenv.config()
8+
let accessToken = ''
9+
let loggedinUserID = ''
10+
let authUrl = ''
11+
let codeChallenge = ''
12+
let codeChallengeMethod = ''
13+
let authCode
14+
let authtoken = ''
15+
let redirectUrl = ''
16+
let refreshToken = ''
17+
const client = contentstackClient()
18+
const oauthClient = client.oauth({
19+
clientId: process.env.CLIENT_ID,
20+
appId: process.env.APP_ID,
21+
redirectUri: process.env.REDIRECT_URI
22+
})
23+
24+
describe('OAuth Authentication API Test', () => {
25+
it('should login with credentials', done => {
26+
client.login({ email: process.env.EMAIL, password: process.env.PASSWORD }, { include_orgs: true, include_orgs_roles: true, include_stack_roles: true, include_user_settings: true }).then((response) => {
27+
expect(response.notice).to.be.equal('Login Successful.', 'Login success messsage does not match.')
28+
done()
29+
})
30+
.catch(done)
31+
})
32+
33+
it('should get Current user info test', done => {
34+
client.getUser().then((user) => {
35+
authtoken = user.authtoken
36+
done()
37+
})
38+
.catch(done)
39+
})
40+
41+
it('should fail when trying to login with invalid app credentials', () => {
42+
try {
43+
client.oauth({
44+
clientId: 'clientId',
45+
appId: 'appId',
46+
redirectUri: 'redirectUri'
47+
})
48+
} catch (error) {
49+
const jsonMessage = JSON.parse(error.message)
50+
expect(jsonMessage.status).to.be.equal(401, 'Status code does not match for invalid credentials')
51+
expect(jsonMessage.errorMessage).to.not.equal(null, 'Error message not proper')
52+
expect(jsonMessage.errorCode).to.be.equal(104, 'Error code does not match')
53+
}
54+
})
55+
56+
it('should generate OAuth authorization URL', async () => {
57+
authUrl = await oauthClient.authorize()
58+
const url = new URL(authUrl)
59+
60+
codeChallenge = url.searchParams.get('code_challenge')
61+
codeChallengeMethod = url.searchParams.get('code_challenge_method')
62+
63+
// Ensure they are not empty strings
64+
expect(codeChallenge).to.not.equal('')
65+
expect(codeChallengeMethod).to.not.equal('')
66+
expect(authUrl).to.include(process.env.CLIENT_ID, 'Client ID mismatch')
67+
})
68+
69+
it('should simulate calling the authorization URL and receive authorization code', async () => {
70+
try {
71+
const authorizationEndpoint = oauthClient.axiosInstance.defaults.developerHubBaseUrl
72+
axios.defaults.headers.common.authtoken = authtoken
73+
axios.defaults.headers.common.organization_uid = process.env.ORGANIZATION
74+
const response = await axios
75+
.post(`${authorizationEndpoint}/manifests/${process.env.APP_ID}/authorize`, {
76+
client_id: process.env.CLIENT_ID,
77+
redirect_uri: process.env.REDIRECT_URI,
78+
code_challenge: codeChallenge,
79+
code_challenge_method: codeChallengeMethod,
80+
response_type: 'code'
81+
})
82+
const data = response.data
83+
redirectUrl = data.data.redirect_url
84+
const url = new URL(redirectUrl)
85+
authCode = url.searchParams.get('code')
86+
oauthClient.axiosInstance.oauth.appId = process.env.APP_ID
87+
oauthClient.axiosInstance.oauth.clientId = process.env.CLIENT_ID
88+
oauthClient.axiosInstance.oauth.redirectUri = process.env.REDIRECT_URI
89+
// Ensure they are not empty strings
90+
expect(redirectUrl).to.not.equal('')
91+
expect(url).to.not.equal('')
92+
} catch (error) {
93+
console.log(error)
94+
}
95+
})
96+
97+
it('should exchange authorization code for access token', async () => {
98+
const response = await oauthClient.exchangeCodeForToken(authCode)
99+
accessToken = response.access_token
100+
loggedinUserID = response.user_uid
101+
refreshToken = response.refresh_token
102+
103+
expect(response.organization_uid).to.be.equal(process.env.ORGANIZATION, 'Organization mismatch')
104+
// eslint-disable-next-line no-unused-expressions
105+
expect(response.access_token).to.not.be.null
106+
// eslint-disable-next-line no-unused-expressions
107+
expect(response.refresh_token).to.not.be.null
108+
})
109+
110+
it('should get the logged-in user info using the access token', async () => {
111+
const user = await client.getUser({
112+
authorization: `Bearer ${accessToken}`
113+
})
114+
expect(user.uid).to.be.equal(loggedinUserID)
115+
expect(user.email).to.be.equal(process.env.EMAIL, 'Email mismatch')
116+
})
117+
118+
it('should refresh the access token using refresh token', async () => {
119+
const response = await oauthClient.refreshAccessToken(refreshToken)
120+
121+
accessToken = response.access_token
122+
refreshToken = response.refresh_token
123+
// eslint-disable-next-line no-unused-expressions
124+
expect(response.access_token).to.not.be.null
125+
// eslint-disable-next-line no-unused-expressions
126+
expect(response.refresh_token).to.not.be.null
127+
})
128+
129+
it('should logout successfully after OAuth authentication', async () => {
130+
const response = await oauthClient.logout()
131+
expect(response).to.be.equal('Logged out successfully')
132+
})
133+
134+
it('should fail to make an API request with an expired token', async () => {
135+
try {
136+
await client.getUser({
137+
authorization: `Bearer ${accessToken}`
138+
})
139+
} catch (error) {
140+
expect(error.status).to.be.equal(401, 'API request should fail with status 401')
141+
expect(error.errorMessage).to.be.equal('The provided access token is invalid or expired or revoked', 'Error message mismatch')
142+
}
143+
})
144+
})

test/sanity-check/sanity.js

+1
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ require('./api/contentType-delete-test')
2929
require('./api/delete-test')
3030
require('./api/team-test')
3131
require('./api/auditlog-test')
32+
require('./api/oauth-test')

test/unit/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ require('./variantGroup-test')
4444
require('./ungroupedVariants-test')
4545
require('./variantsWithVariantsGroup-test')
4646
require('./variants-entry-test')
47+
require('./oauthHandler-test')

0 commit comments

Comments
 (0)