Skip to content
Open
27 changes: 22 additions & 5 deletions packages/core/src/components/AuthControls.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import AuthControls from './AuthControls.vue'
import { authStoresCache } from '../runtime/auth'

describe('AuthControls', () => {
beforeEach(() => sessionStorage.clear())
afterEach(() => sessionStorage.clear())
beforeEach(() => {
sessionStorage.clear()
authStoresCache.clear()
})
afterEach(() => {
sessionStorage.clear()
authStoresCache.clear()
})

it('persists the entered credential to sessionStorage immediately on input', async () => {
const wrapper = mount(AuthControls, { props: { specName: 'public', scheme: 'bearer' } })
const input = wrapper.find('input')
await input.setValue('SECRET_TOKEN')
expect(JSON.parse(sessionStorage.getItem('vod:auth:public')!)).toMatchObject({
scheme: 'bearer',
value: 'SECRET_TOKEN',
})
})

it('persists the entered credential to sessionStorage on blur', async () => {
const wrapper = mount(AuthControls, { props: { specName: 'public', scheme: 'bearer' } })
Expand All @@ -26,11 +43,11 @@ describe('AuthControls', () => {
})

it('clears stored credentials when the button is clicked', async () => {
sessionStorage.setItem('vod:auth:public', JSON.stringify({ scheme: 'bearer', value: 'X' }))
const wrapper = mount(AuthControls, { props: { specName: 'public', scheme: 'bearer' } })
sessionStorage.setItem('vod:auth:clear-test', JSON.stringify({ scheme: 'bearer', value: 'X' }))
const wrapper = mount(AuthControls, { props: { specName: 'clear-test', scheme: 'bearer' } })
await nextTick()
await wrapper.find('button.vod-auth__clear').trigger('click')
expect(sessionStorage.getItem('vod:auth:public')).toBeNull()
expect(sessionStorage.getItem('vod:auth:clear-test')).toBeNull()
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('')
})

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/AuthControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
class="vod-auth__input"
autocomplete="off"
spellcheck="false"
@input="commit"
@blur="commit"
@keydown.enter.prevent="commit"
/>
Expand Down
264 changes: 264 additions & 0 deletions packages/core/src/components/EndpointTryPanel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import EndpointTryPanel from './EndpointTryPanel.vue'
import { useAuthState, authStoresCache } from '../runtime/auth'
import type { ParsedOperation } from '../parser/types'
import EndpointPlayground from './EndpointPlayground.vue'

const dummyOp: ParsedOperation = {
id: 'test.op',
operationId: 'testOp',
kind: 'path',
method: 'get',
path: '/test',
summary: 'Test',
tags: [],
parameters: [],
responses: [],
requestSchemaRefs: {},
responseSchemaRefs: {},
defaultServer: '',
security: [],
deprecated: false,
}

describe('EndpointTryPanel - Authorization Injection', () => {
beforeEach(() => {
sessionStorage.clear()
authStoresCache.clear()
})

afterEach(() => {
sessionStorage.clear()
authStoresCache.clear()
})

it('injects bearer token correctly', async () => {
const authState = useAuthState('test-spec')
authState.set({ scheme: 'bearer', value: 'my-bearer-token' })
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'bearer',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
expect(playground.exists()).toBe(true)

const envelope = { url: 'http://api.example.com/test', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.init.headers).toEqual({
Authorization: 'Bearer my-bearer-token',
})
})

it('injects oauth2 token correctly', async () => {
const authState = useAuthState('test-spec')
authState.set({ scheme: 'oauth2', value: 'oauth-token' })
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'oauth2',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
const envelope = { url: 'http://api.example.com/test', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.init.headers).toEqual({
Authorization: 'Bearer oauth-token',
})
})

it('injects basic auth correctly', async () => {
const authState = useAuthState('test-spec')
authState.set({ scheme: 'basic', value: 'user:pass' })
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'basic',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
const envelope = { url: 'http://api.example.com/test', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.init.headers).toEqual({
Authorization: `Basic ${btoa('user:pass')}`,
})
})

it('injects apikey as header correctly', async () => {
const authState = useAuthState('test-spec')
authState.set({
scheme: 'apikey',
value: 'my-api-key',
headerName: 'X-API-Key',
apiKeyIn: 'header',
})
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'apikey',
headerName: 'X-API-Key',
apiKeyIn: 'header',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
const envelope = { url: 'http://api.example.com/test', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.init.headers).toEqual({
'X-API-Key': 'my-api-key',
})
})

it('injects apikey as query parameter correctly', async () => {
const authState = useAuthState('test-spec')
authState.set({
scheme: 'apikey',
value: 'my-api-key',
headerName: 'api_key',
apiKeyIn: 'query',
})
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'apikey',
headerName: 'api_key',
apiKeyIn: 'query',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
const envelope = { url: 'http://api.example.com/test', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.url).toBe('http://api.example.com/test?api_key=my-api-key')
})

it('injects apikey as query parameter with existing params correctly', async () => {
const authState = useAuthState('test-spec')
authState.set({
scheme: 'apikey',
value: 'my-api-key',
headerName: 'api_key',
apiKeyIn: 'query',
})
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'apikey',
headerName: 'api_key',
apiKeyIn: 'query',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
const envelope = { url: 'http://api.example.com/test?foo=bar', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.url).toBe('http://api.example.com/test?foo=bar&api_key=my-api-key')
})

it('does not inject apikey for cookie type in browser requests', async () => {
const authState = useAuthState('test-spec')
authState.set({
scheme: 'apikey',
value: 'my-cookie-val',
headerName: 'cookie_name',
apiKeyIn: 'cookie',
})
await nextTick()

const wrapper = mount(EndpointTryPanel, {
props: {
op: dummyOp,
specName: 'test-spec',
specVersionLabel: 'v1',
serverList: ['http://api.example.com'],
scheme: 'apikey',
headerName: 'cookie_name',
apiKeyIn: 'cookie',
bodyInputs: false,
inline: false,
showSnippets: false,
showAuth: true,
showTry: true,
},
})

const playground = wrapper.findComponent(EndpointPlayground)
const envelope = { url: 'http://api.example.com/test', init: { headers: {} } }
playground.vm.$emit('before-send', envelope)

expect(envelope.init.headers).toEqual({})
expect(envelope.url).toBe('http://api.example.com/test')
})
})
20 changes: 19 additions & 1 deletion packages/core/src/runtime/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { defineComponent, h, nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import { readStoredCredential, useAuthState, type AuthState } from './auth'
import { readStoredCredential, useAuthState, authStoresCache, type AuthState } from './auth'

function withAuth(specName: string, callback: (state: AuthState) => void) {
const Harness = defineComponent({
Expand All @@ -17,10 +17,12 @@ function withAuth(specName: string, callback: (state: AuthState) => void) {
describe('useAuthState', () => {
beforeEach(() => {
sessionStorage.clear()
authStoresCache.clear()
})

afterEach(() => {
sessionStorage.clear()
authStoresCache.clear()
})

it('starts with no credential when storage is empty', async () => {
Expand Down Expand Up @@ -117,4 +119,20 @@ describe('useAuthState', () => {
await nextTick()
expect(secondState?.credential.value?.value).toBe('KEEP_ME')
})

it('shares reactive credential state across multiple active hook instances', async () => {
let state1: AuthState | undefined
let state2: AuthState | undefined
withAuth('shared-spec', (s) => {
state1 = s
})
withAuth('shared-spec', (s) => {
state2 = s
})

state1?.set({ scheme: 'bearer', value: 'UPDATED_TOKEN' })
await nextTick()

expect(state2?.credential.value?.value).toBe('UPDATED_TOKEN')
})
})
Loading