Skip to content

Commit 7284196

Browse files
committed
add iam and sts unit tests
1 parent a6d98d3 commit 7284196

File tree

4 files changed

+552
-63
lines changed

4 files changed

+552
-63
lines changed

packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ describe('AuthUtil', async function () {
135135
it('returns credentials form for IAM credentials', async function () {
136136
sinon.stub(auth, 'isSsoSession').returns(false)
137137
sinon.stub(auth, 'isConnected').returns(true)
138+
sinon.stub(auth, 'isIamSession').returns(true)
138139

139140
const forms = await auth.getAuthFormIds()
140141
assert.deepStrictEqual(forms, ['credentials'])
@@ -406,4 +407,189 @@ describe('AuthUtil', async function () {
406407
)
407408
})
408409
})
409-
})
410+
411+
describe('login_iam', function () {
412+
it('creates IAM session and logs in', async function () {
413+
const mockResponse = {
414+
id: 'test-credential-id',
415+
credentials: {
416+
accessKeyId: 'encrypted-access-key',
417+
secretAccessKey: 'encrypted-secret-key',
418+
sessionToken: 'encrypted-session-token',
419+
},
420+
updateCredentialsParams: {
421+
data: 'credential-data',
422+
},
423+
}
424+
425+
const mockIamLogin = {
426+
login: sinon.stub().resolves(mockResponse),
427+
loginType: 'iam',
428+
}
429+
430+
sinon.stub(auth2, 'IamLogin').returns(mockIamLogin as any)
431+
432+
const response = await auth.login_iam('accessKey', 'secretKey', 'sessionToken')
433+
434+
assert.ok(mockIamLogin.login.calledOnce)
435+
assert.ok(mockIamLogin.login.calledWith({
436+
accessKey: 'accessKey',
437+
secretKey: 'secretKey',
438+
sessionToken: 'sessionToken',
439+
roleArn: undefined,
440+
}))
441+
assert.strictEqual(response, mockResponse)
442+
})
443+
444+
it('creates IAM session with role ARN', async function () {
445+
const mockResponse = {
446+
id: 'test-credential-id',
447+
credentials: {
448+
accessKeyId: 'encrypted-access-key',
449+
secretAccessKey: 'encrypted-secret-key',
450+
sessionToken: 'encrypted-session-token',
451+
},
452+
updateCredentialsParams: {
453+
data: 'credential-data',
454+
},
455+
}
456+
457+
const mockIamLogin = {
458+
login: sinon.stub().resolves(mockResponse),
459+
loginType: 'iam',
460+
}
461+
462+
sinon.stub(auth2, 'IamLogin').returns(mockIamLogin as any)
463+
464+
const response = await auth.login_iam('accessKey', 'secretKey', 'sessionToken', 'arn:aws:iam::123456789012:role/TestRole')
465+
466+
assert.ok(mockIamLogin.login.calledOnce)
467+
assert.ok(mockIamLogin.login.calledWith({
468+
accessKey: 'accessKey',
469+
secretKey: 'secretKey',
470+
sessionToken: 'sessionToken',
471+
roleArn: 'arn:aws:iam::123456789012:role/TestRole',
472+
}))
473+
assert.strictEqual(response, mockResponse)
474+
})
475+
})
476+
477+
describe('getIamCredential', function () {
478+
it('returns IAM credentials from session', async function () {
479+
const mockCredentials = {
480+
accessKeyId: 'test-access-key',
481+
secretAccessKey: 'test-secret-key',
482+
sessionToken: 'test-session-token',
483+
}
484+
485+
const mockSession = {
486+
getCredential: sinon.stub().resolves({
487+
credential: mockCredentials,
488+
updateCredentialsParams: { data: 'test' },
489+
}),
490+
loginType: 'iam',
491+
}
492+
493+
;(auth as any).session = mockSession
494+
495+
const result = await auth.getIamCredential()
496+
497+
assert.ok(mockSession.getCredential.calledOnce)
498+
assert.deepStrictEqual(result, mockCredentials)
499+
})
500+
501+
it('throws error for SSO session', async function () {
502+
const mockSession = {
503+
getCredential: sinon.stub().resolves({
504+
credential: 'sso-token',
505+
updateCredentialsParams: { data: 'test' },
506+
}),
507+
loginType: 'sso',
508+
}
509+
510+
;(auth as any).session = mockSession
511+
512+
try {
513+
await auth.getIamCredential()
514+
assert.fail('Should have thrown an error')
515+
} catch (err) {
516+
assert.strictEqual((err as Error).message, 'Cannot get token with SSO session')
517+
}
518+
})
519+
520+
it('throws error when not logged in', async function () {
521+
;(auth as any).session = undefined
522+
523+
try {
524+
await auth.getIamCredential()
525+
assert.fail('Should have thrown an error')
526+
} catch (err) {
527+
assert.strictEqual((err as Error).message, 'Cannot get credential without logging in.')
528+
}
529+
})
530+
})
531+
532+
describe('isIamSession', function () {
533+
it('returns true for IAM session', function () {
534+
const mockSession = { loginType: 'iam' }
535+
;(auth as any).session = mockSession
536+
537+
assert.strictEqual(auth.isIamSession(), true)
538+
})
539+
540+
it('returns false for SSO session', function () {
541+
const mockSession = { loginType: 'sso' }
542+
;(auth as any).session = mockSession
543+
544+
assert.strictEqual(auth.isIamSession(), false)
545+
})
546+
547+
it('returns false when no session', function () {
548+
;(auth as any).session = undefined
549+
550+
assert.strictEqual(auth.isIamSession(), false)
551+
})
552+
})
553+
554+
describe('IAM session state changes', function () {
555+
let mockLspAuth: any
556+
557+
beforeEach(function () {
558+
mockLspAuth = (auth as any).lspAuth
559+
})
560+
561+
it('updates IAM credential when state is refreshed', async function () {
562+
const mockSession = {
563+
getCredential: sinon.stub().resolves({
564+
credential: { accessKeyId: 'key', secretAccessKey: 'secret' },
565+
updateCredentialsParams: { data: 'fake-data' },
566+
}),
567+
loginType: 'iam',
568+
}
569+
;(auth as any).session = mockSession
570+
571+
await (auth as any).stateChangeHandler({ state: 'refreshed' })
572+
573+
assert.ok(mockLspAuth.updateIamCredential.called)
574+
assert.strictEqual(mockLspAuth.updateIamCredential.firstCall.args[0].data, 'fake-data')
575+
})
576+
577+
it('cleans up IAM credential when connection expires', async function () {
578+
const mockSession = { loginType: 'iam' }
579+
;(auth as any).session = mockSession
580+
581+
await (auth as any).stateChangeHandler({ state: 'expired' })
582+
583+
assert.ok(mockLspAuth.deleteIamCredential.called)
584+
})
585+
586+
it('deletes IAM credential when disconnected', async function () {
587+
const mockSession = { loginType: 'iam' }
588+
;(auth as any).session = mockSession
589+
590+
await (auth as any).stateChangeHandler({ state: 'notConnected' })
591+
592+
assert.ok(mockLspAuth.deleteIamCredential.called)
593+
})
594+
})
595+
})

packages/core/src/test/amazonqDoc/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export async function sessionWriteFile(session: Session, uri: vscode.Uri, encode
107107
export function createMockAuthUtil(sandbox: sinon.SinonSandbox) {
108108
const mockLspAuth: Partial<LanguageClientAuth> = {
109109
registerSsoTokenChangedHandler: sinon.stub().resolves(),
110+
registerStsCredentialChangedHandler: sinon.stub().resolves(),
110111
}
111112
AuthUtil.create(mockLspAuth as LanguageClientAuth)
112113
sandbox.stub(AuthUtil.instance.regionProfileManager, 'onDidChangeRegionProfile').resolves()

0 commit comments

Comments
 (0)