Skip to content

Commit

Permalink
Expose routes over OpenAPI
Browse files Browse the repository at this point in the history
Signed-off-by: Matteo Collina <[email protected]>
  • Loading branch information
mcollina committed May 8, 2024
1 parent 34df76a commit 8a8f355
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 36 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=true
11 changes: 6 additions & 5 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ const stackable: Stackable<AiWarpConfig> = async function (fastify, opts) {
await fastify.register(fastifyUser as any, config.auth)
await fastify.register(authPlugin, opts)

await fastify.register(warpPlugin, opts) // needs to be registered here for fastify.ai to be decorated

await fastify.register(rateLimitPlugin, opts)
await fastify.register(apiPlugin, opts)

if (config.showAiWarpHomepage !== undefined && config.showAiWarpHomepage) {
await fastify.register(fastifyStatic, {
root: join(import.meta.dirname, 'static')
})
}

await fastify.register(platformaticService, opts)

await fastify.register(warpPlugin, opts) // needs to be registered here for fastify.ai to be decorated

await fastify.register(rateLimitPlugin, opts)
await fastify.register(apiPlugin, opts)

Check failure on line 30 in index.ts

View workflow job for this annotation

GitHub Actions / Linting

Block must not be padded by blank lines

}

stackable.configType = 'ai-warp-app'
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"lint-md": "markdownlint-cli2 .",
"lint-md:fix": "markdownlint-cli2 --fix .",
"test": "npm run test:unit && npm run test:e2e && npm run test:types",
"test:unit": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx ./tests/unit/index.ts",
"test:e2e": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx ./tests/e2e/index.ts",
"test:unit": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx --test-concurrency=1 ./tests/unit/*",
"test:e2e": "node --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout --import=tsx --test-concurrency=1 ./tests/e2e/*",
"test:types": "tsd"
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
response: Type.String()
}),
default: Type.Object({
code: Type.String(),
code: Type.Optional(Type.String()),
message: Type.String()
})
}
Expand Down
63 changes: 62 additions & 1 deletion tests/e2e/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript
* eslint/no-floating-promises */
import { before, after, describe, it } from 'node:test'
import assert from 'node:assert'
import { FastifyInstance } from 'fastify'
Expand All @@ -8,9 +9,12 @@ import { buildAiWarpApp } from '../utils/stackable.js'
import { AZURE_DEPLOYMENT_NAME, AZURE_MOCK_HOST } from '../utils/mocks/azure.js'
import { MOCK_CONTENT_RESPONSE, buildExpectedStreamBodyString } from '../utils/mocks/base.js'
import { OLLAMA_MOCK_HOST } from '../utils/mocks/ollama.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const expectedStreamBody = buildExpectedStreamBodyString()


interface Provider {
name: string
config: AiWarpConfig['aiProvider']
Expand Down Expand Up @@ -108,6 +112,7 @@ for (const { name, config } of providers) {
prompt: 'asd'
})
})

assert.strictEqual(res.headers.get('content-type'), 'text/event-stream')

assert.strictEqual(chunkCallbackCalled, true)
Expand Down Expand Up @@ -170,3 +175,59 @@ it('calls the preResponseCallback', async () => {

await app.close()
})

it('provides all paths in OpenAPI', async () => {
const [app, port] = await buildAiWarpApp({
aiProvider: {
openai: {
model: 'gpt-3.5-turbo',
apiKey: ''
}
}
})

await app.start()

const res = await fetch(`http://localhost:${port}/documentation/json`)
const body = await res.json()

assert.deepStrictEqual(Object.keys(body.paths), [
'/api/v1/prompt',
'/api/v1/stream'
])

await app.close()
})

it('prompt with wrong JSON', async () => {
const [app, port] = await buildAiWarpApp({
aiProvider: {
openai: {
model: 'gpt-3.5-turbo',
apiKey: ''
}
}
})

await app.start()

const res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
}).slice(0, 10)
})

assert.strictEqual(res.status, 400)

const body = await res.json()

assert.deepStrictEqual(body, {
message: 'Unexpected end of JSON input'
})

await app.close()
})
2 changes: 2 additions & 0 deletions tests/e2e/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import assert from 'node:assert'
import { buildAiWarpApp } from '../utils/stackable.js'
import { AiWarpConfig } from '../../config.js'
import { authConfig, createToken } from '../utils/auth.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const aiProvider: AiWarpConfig['aiProvider'] = {
openai: {
Expand Down
6 changes: 0 additions & 6 deletions tests/e2e/index.ts

This file was deleted.

78 changes: 65 additions & 13 deletions tests/e2e/rate-limiting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import fastifyPlugin from 'fastify-plugin'
import { AiWarpConfig } from '../../config.js'
import { buildAiWarpApp } from '../utils/stackable.js'
import { authConfig, createToken } from '../utils/auth.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const aiProvider: AiWarpConfig['aiProvider'] = {
openai: {
Expand All @@ -28,7 +30,15 @@ it('calls ai.rateLimiting.max callback', async () => {

await app.start()

const res = await fetch(`http://localhost:${port}`)
const res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
assert.strictEqual(res.headers.get('x-ratelimit-limit'), `${expectedMax}`)
} finally {
Expand All @@ -50,7 +60,15 @@ it('calls ai.rateLimiting.allowList callback', async () => {

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
} finally {
await app.close()
Expand All @@ -76,13 +94,21 @@ it('calls ai.rateLimiting.onBanReach callback', async () => {

app.ai.rateLimiting.errorResponseBuilder = () => {
errorResponseBuilderCalled = true
return { error: 'rate limited' }
return { message: 'rate limited' }
}
}))

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(onBanReachCalled, true)
assert.strictEqual(errorResponseBuilderCalled, true)
} finally {
Expand All @@ -104,7 +130,15 @@ it('calls ai.rateLimiting.keyGenerator callback', async () => {

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
} finally {
await app.close()
Expand All @@ -120,13 +154,21 @@ it('calls ai.rateLimiting.errorResponseBuilder callback', async () => {
app.ai.rateLimiting.max = () => 0
app.ai.rateLimiting.errorResponseBuilder = () => {
callbackCalled = true
return { error: 'rate limited' }
return { message: 'rate limited' }
}
}))

await app.start()

await fetch(`http://localhost:${port}`)
await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(callbackCalled, true)
} finally {
await app.close()
Expand Down Expand Up @@ -156,17 +198,27 @@ it('uses the max for a specific claim', async () => {
try {
await app.start()

let res = await fetch(`http://localhost:${port}`, {
let res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
Authorization: `Bearer ${createToken({ rateLimitMax: '10' })}`
}
Authorization: `Bearer ${createToken({ rateLimitMax: '10' })}`,
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(res.headers.get('x-ratelimit-limit'), '10')

res = await fetch(`http://localhost:${port}`, {
res = await fetch(`http://localhost:${port}/api/v1/prompt`, {
method: 'POST',
headers: {
Authorization: `Bearer ${createToken({ rateLimitMax: '100' })}`
}
Authorization: `Bearer ${createToken({ rateLimitMax: '100' })}`,
'content-type': 'application/json'
},
body: JSON.stringify({
prompt: 'asd'
})
})
assert.strictEqual(res.headers.get('x-ratelimit-limit'), '100')
} finally {
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/ai-providers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { MOCK_CONTENT_RESPONSE, buildExpectedStreamBodyString } from '../utils/m
import { OLLAMA_MOCK_HOST } from '../utils/mocks/ollama.js'
import { AZURE_DEPLOYMENT_NAME, AZURE_MOCK_HOST } from '../utils/mocks/azure.js'
import { mockLlama2 } from '../utils/mocks/llama2.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const expectedStreamBody = buildExpectedStreamBodyString()

Expand Down
2 changes: 2 additions & 0 deletions tests/unit/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { join } from 'node:path'
import AiWarpGenerator from '../../lib/generator.js'
import { generateGlobalTypesFile } from '../../lib/templates/types.js'
import { generatePluginWithTypesSupport } from '@platformatic/generators/lib/create-plugin.js'
import { mockAllProviders } from '../utils/mocks/index.js'
mockAllProviders()

const tempDirBase = join(import.meta.dirname, 'tmp')

Expand Down
5 changes: 0 additions & 5 deletions tests/unit/index.ts

This file was deleted.

7 changes: 4 additions & 3 deletions tests/utils/stackable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export async function buildAiWarpApp (config: AiWarpConfig): Promise<[FastifyIns
server: {
port,
forceCloseConnections: true,
healthCheck: {
enabled: false
},
healthCheck: false,
logger: {
level: 'silent'
}
},
service: {
openapi: true
},
...config
}, stackable)

Expand Down

0 comments on commit 8a8f355

Please sign in to comment.