feat: refactor and WIP add e2e tests

pull/715/head
Travis Fischer 2025-06-03 16:48:24 +07:00
rodzic 6b6f934911
commit e2eeefef89
18 zmienionych plików z 340 dodań i 76 usunięć

Wyświetl plik

@ -16,6 +16,13 @@ import { getGitHubClient } from '@/lib/external/github'
export const authRouter = issuer({
subjects,
storage: DrizzleAuthStorage(),
ttl: {
access: 60 * 60 * 24 * 30, // 30 days
refresh: 60 * 60 * 24 * 365 // 1 year
// Used for creating longer-lived testing tokens
// access: 60 * 60 * 24 * 366, // 1 year
// refresh: 60 * 60 * 24 * 365 * 5 // 5 years
},
providers: {
github: GithubProvider({
clientID: env.GITHUB_CLIENT_ID,

Wyświetl plik

@ -5,4 +5,4 @@
# a local .env file in order to run this project.
# ------------------------------------------------------------------------------
# TODO
AGENTIC_API_REFRESH_TOKEN=

Wyświetl plik

@ -0,0 +1,51 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { loadAgenticConfig } from '@agentic/platform'
import pMap from 'p-map'
import { client } from '../src'
const fixtures = [
// 'basic-raw-free-ts',
// 'basic-raw-free-json',
// 'pricing-freemium',
// 'pricing-pay-as-you-go',
// 'pricing-3-plans',
// 'pricing-monthly-annual',
// 'pricing-custom-0',
'basic-openapi'
]
const fixturesDir = path.join(
fileURLToPath(import.meta.url),
'..',
'..',
'..',
'..',
'packages',
'fixtures'
)
const validFixturesDir = path.join(fixturesDir, 'valid')
async function main() {
const deployments = await pMap(
fixtures,
async (fixture) => {
const fixtureDir = path.join(validFixturesDir, fixture)
const config = await loadAgenticConfig({ cwd: fixtureDir })
console.log(config)
const deployment = await client.createDeployment(config)
return deployment
},
{
concurrency: 1
}
)
console.log(JSON.stringify(deployments, null, 2))
}
await main()

Wyświetl plik

@ -20,6 +20,10 @@
"devDependencies": {
"@agentic/platform": "workspace:*",
"@agentic/platform-api-client": "workspace:*",
"@agentic/platform-core": "workspace:*",
"@agentic/platform-fixtures": "workspace:*"
},
"dependencies": {
"p-map": "catalog:"
}
}

Wyświetl plik

@ -0,0 +1,8 @@
import { AgenticApiClient } from '@agentic/platform-api-client'
import { env } from './env'
export const client = new AgenticApiClient({
apiBaseUrl: env.AGENTIC_API_BASE_URL,
apiKey: env.AGENTIC_API_ACCESS_TOKEN
})

Wyświetl plik

@ -1,20 +1,19 @@
import 'dotenv/config'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { loadAgenticConfig } from '@agentic/platform'
import { AgenticApiClient } from '@agentic/platform-api-client'
import { describe, expect, test } from 'vitest'
import { client } from './client'
const fixtures = [
'basic-raw-free-ts',
'basic-raw-free-json',
'pricing-freemium',
'pricing-pay-as-you-go',
'pricing-3-plans',
'pricing-monthly-annual',
'pricing-custom-0',
// 'basic-raw-free-ts',
// 'basic-raw-free-json',
// 'pricing-freemium',
// 'pricing-pay-as-you-go',
// 'pricing-3-plans',
// 'pricing-monthly-annual',
// 'pricing-custom-0',
'basic-openapi'
]
@ -29,11 +28,6 @@ const fixturesDir = path.join(
)
const validFixturesDir = path.join(fixturesDir, 'valid')
const client = new AgenticApiClient({
apiBaseUrl: process.env.AGENTIC_API_BASE_URL
})
await client.setRefreshAuthToken(process.env.AGENTIC_API_REFRESH_TOKEN!)
for (const fixture of fixtures) {
test(
`${fixture}`,
@ -44,7 +38,7 @@ for (const fixture of fixtures) {
const fixtureDir = path.join(validFixturesDir, fixture)
const config = await loadAgenticConfig({ cwd: fixtureDir })
expect(config).toMatchSnapshot()
const deployment = await client.createDeployment(config)
}
)
}

Wyświetl plik

@ -0,0 +1,21 @@
import 'dotenv/config'
import { parseZodSchema } from '@agentic/platform-core'
import { z } from 'zod'
export const envSchema = z.object({
NODE_ENV: z
.enum(['development', 'test', 'production'])
.default('development'),
AGENTIC_API_BASE_URL: z.string().url().optional(),
AGENTIC_API_ACCESS_TOKEN: z.string().nonempty()
})
// eslint-disable-next-line no-process-env
export const env = parseZodSchema(envSchema, process.env, {
error: 'Invalid environment variables'
})
export const isDev = env.NODE_ENV === 'development'
export const isProd = env.NODE_ENV === 'production'

Wyświetl plik

@ -0,0 +1,2 @@
export * from './client'
export * from './env'

Wyświetl plik

@ -1,5 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src", "*.config.ts"],
"include": ["src", "bin", "*.config.ts"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -7,7 +7,7 @@
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git"
},
"packageManager": "pnpm@10.11.0",
"packageManager": "pnpm@10.11.1",
"engines": {
"node": ">=22"
},

Wyświetl plik

@ -68,13 +68,15 @@ export class AgenticApiClient {
hooks: {
beforeRequest: [
async (request) => {
// Always verify freshness of auth tokens before making a request
await this.verifyAuthAndRefreshIfNecessary()
assert(this._authTokens, 'Not authenticated')
request.headers.set(
'Authorization',
`Bearer ${this._authTokens.access}`
)
if (!this.apiKey && this._authTokens) {
// Always verify freshness of auth tokens before making a request
await this.verifyAuthAndRefreshIfNecessary()
assert(this._authTokens, 'Not authenticated')
request.headers.set(
'Authorization',
`Bearer ${this._authTokens.access}`
)
}
}
]
}
@ -89,15 +91,11 @@ export class AgenticApiClient {
return this._authTokens
}
async setRefreshAuthToken(refreshToken: string): Promise<void> {
async setAuth(tokens: AuthTokens): Promise<AuthUser> {
this._ensureNoApiKey()
const result = await this._authClient.refresh(refreshToken)
if (result.err) {
throw result.err
}
this._authTokens = result.tokens
this._authTokens = tokens
return this.verifyAuthAndRefreshIfNecessary()
}
async verifyAuthAndRefreshIfNecessary(): Promise<AuthUser> {

Wyświetl plik

@ -22,10 +22,7 @@ async function main() {
apiBaseUrl: process.env.AGENTIC_API_BASE_URL,
onUpdateAuth: (update) => {
if (update) {
AuthStore.setAuth({
refreshToken: update.session.refresh,
user: update.user
})
AuthStore.setAuth(update)
} else {
AuthStore.clearAuth()
}
@ -36,7 +33,7 @@ async function main() {
const authSession = AuthStore.tryGetAuth()
if (authSession) {
try {
await client.setRefreshAuthToken(authSession.refreshToken)
await client.setAuth(authSession.session)
} catch {
console.warn('Existing auth session is invalid; logging out.\n')
AuthStore.clearAuth()
@ -45,7 +42,7 @@ async function main() {
// Initialize the CLI program
const program = new Command('agentic')
.option('-j, --json', 'Print output in JSON format')
.option('-j, --json', 'Print output as JSON')
.showHelpAfterError()
const logger = {

Wyświetl plik

@ -59,7 +59,7 @@ export async function auth({
// AuthStore should be updated via the onUpdateAuth callback
const session = AuthStore.tryGetAuth()
assert(session && session.refreshToken === client.authTokens.refresh)
assert(session && session.session.access === client.authTokens.access)
_resolveAuth(session)
return c.text(

Wyświetl plik

@ -1,4 +1,8 @@
import type { AgenticApiClient, AuthUser } from '@agentic/platform-api-client'
import type {
AgenticApiClient,
AuthTokens,
AuthUser
} from '@agentic/platform-api-client'
import type { Command } from 'commander'
export type Context = {
@ -12,6 +16,6 @@ export type Context = {
}
export type AuthSession = {
refreshToken: string
session: AuthTokens
user: AuthUser
}

Wyświetl plik

@ -72,7 +72,6 @@
{
"required": true,
"schema": {
"title": "postId",
"type": "string"
},
"name": "postId",

Wyświetl plik

@ -17,7 +17,12 @@ exports[`getToolsFromOpenAPISpec > basic.json 1`] = `
{
"description": "Echo",
"inputSchema": {
"properties": {},
"properties": {
"name": {
"title": "name",
"type": "string",
},
},
"required": [],
"type": "object",
},
@ -531,8 +536,15 @@ exports[`getToolsFromOpenAPISpec > firecrawl.json 1`] = `
{
"description": "Get the status of a crawl job",
"inputSchema": {
"properties": {},
"required": [],
"properties": {
"jobId": {
"description": "ID of the crawl job",
"type": "string",
},
},
"required": [
"jobId",
],
"type": "object",
},
"name": "get_crawl_status",
@ -695,8 +707,15 @@ exports[`getToolsFromOpenAPISpec > firecrawl.json 1`] = `
{
"description": "Cancel a crawl job",
"inputSchema": {
"properties": {},
"required": [],
"properties": {
"jobId": {
"description": "ID of the crawl job",
"type": "string",
},
},
"required": [
"jobId",
],
"type": "object",
},
"name": "cancel_crawl_job",
@ -738,6 +757,14 @@ exports[`getToolsFromOpenAPISpec > mixed.json 1`] = `
"title": "id",
"type": "string",
},
"name": {
"title": "name",
"type": "string",
},
"x-custom-header": {
"title": "x-custom-header",
"type": "string",
},
},
"required": [
"id",
@ -779,8 +806,133 @@ exports[`getToolsFromOpenAPISpec > open-meteo.yaml 1`] = `
{
"description": "7 day weather variables in hourly and daily resolution for given WGS84 latitude and longitude coordinates. Available worldwide.",
"inputSchema": {
"properties": {},
"required": [],
"properties": {
"current_weather": {
"type": "boolean",
},
"daily": {
"items": {
"enum": [
"temperature_2m_max",
"temperature_2m_min",
"apparent_temperature_max",
"apparent_temperature_min",
"precipitation_sum",
"precipitation_hours",
"weather_code",
"sunrise",
"sunset",
"wind_speed_10m_max",
"wind_gusts_10m_max",
"wind_direction_10m_dominant",
"shortwave_radiation_sum",
"uv_index_max",
"uv_index_clear_sky_max",
"et0_fao_evapotranspiration",
],
"type": "string",
},
"type": "array",
},
"hourly": {
"items": {
"enum": [
"temperature_2m",
"relative_humidity_2m",
"dew_point_2m",
"apparent_temperature",
"pressure_msl",
"cloud_cover",
"cloud_cover_low",
"cloud_cover_mid",
"cloud_cover_high",
"wind_speed_10m",
"wind_speed_80m",
"wind_speed_120m",
"wind_speed_180m",
"wind_direction_10m",
"wind_direction_80m",
"wind_direction_120m",
"wind_direction_180m",
"wind_gusts_10m",
"shortwave_radiation",
"direct_radiation",
"direct_normal_irradiance",
"diffuse_radiation",
"vapour_pressure_deficit",
"evapotranspiration",
"precipitation",
"weather_code",
"snow_height",
"freezing_level_height",
"soil_temperature_0cm",
"soil_temperature_6cm",
"soil_temperature_18cm",
"soil_temperature_54cm",
"soil_moisture_0_1cm",
"soil_moisture_1_3cm",
"soil_moisture_3_9cm",
"soil_moisture_9_27cm",
"soil_moisture_27_81cm",
],
"type": "string",
},
"type": "array",
},
"latitude": {
"description": "WGS84 coordinate",
"format": "double",
"type": "number",
},
"longitude": {
"description": "WGS84 coordinate",
"format": "double",
"type": "number",
},
"past_days": {
"description": "If \`past_days\` is set, yesterdays or the day before yesterdays data are also returned.",
"enum": [
1,
2,
],
"type": "integer",
},
"temperature_unit": {
"default": "celsius",
"enum": [
"celsius",
"fahrenheit",
],
"type": "string",
},
"timeformat": {
"default": "iso8601",
"description": "If format \`unixtime\` is selected, all time values are returned in UNIX epoch time in seconds. Please not that all time is then in GMT+0! For daily values with unix timestamp, please apply \`utc_offset_seconds\` again to get the correct date.",
"enum": [
"iso8601",
"unixtime",
],
"type": "string",
},
"timezone": {
"description": "If \`timezone\` is set, all timestamps are returned as local-time and data is returned starting at 0:00 local-time. Any time zone name from the [time zone database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) is supported.",
"type": "string",
},
"wind_speed_unit": {
"default": "kmh",
"enum": [
"kmh",
"ms",
"mph",
"kn",
],
"type": "string",
},
},
"required": [
"latitude",
"longitude",
],
"type": "object",
},
"name": "get_v1_forecast",
@ -1252,7 +1404,14 @@ exports[`getToolsFromOpenAPISpec > pet-store.json 1`] = `
{
"description": "List all pets",
"inputSchema": {
"properties": {},
"properties": {
"limit": {
"description": "How many items to return at one time (max 100)",
"format": "int32",
"maximum": 100,
"type": "integer",
},
},
"required": [],
"type": "object",
},
@ -1267,12 +1426,6 @@ exports[`getToolsFromOpenAPISpec > pet-store.json 1`] = `
"format": "int64",
"type": "integer",
},
"limit": {
"description": "How many items to return at one time (max 100)",
"format": "int32",
"maximum": 100,
"type": "integer",
},
"name": {
"type": "string",
},
@ -1292,8 +1445,15 @@ exports[`getToolsFromOpenAPISpec > pet-store.json 1`] = `
{
"description": "Info for a specific pet",
"inputSchema": {
"properties": {},
"required": [],
"properties": {
"petId": {
"description": "The id of the pet to retrieve",
"type": "string",
},
},
"required": [
"petId",
],
"type": "object",
},
"name": "show_pet_by_id",
@ -1370,7 +1530,20 @@ Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagitt
Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.",
"inputSchema": {
"properties": {},
"properties": {
"limit": {
"description": "maximum number of results to return",
"format": "int32",
"type": "integer",
},
"tags": {
"description": "tags to filter by",
"items": {
"type": "string",
},
"type": "array",
},
},
"required": [],
"type": "object",
},
@ -1381,24 +1554,12 @@ Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condime
"description": "Creates a new pet in the store. Duplicates are allowed",
"inputSchema": {
"properties": {
"limit": {
"description": "maximum number of results to return",
"format": "int32",
"type": "integer",
},
"name": {
"type": "string",
},
"tag": {
"type": "string",
},
"tags": {
"description": "tags to filter by",
"items": {
"type": "string",
},
"type": "array",
},
},
"required": [
"name",
@ -1411,8 +1572,16 @@ Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condime
{
"description": "Returns a user based on a single ID, if the user does not have access to the pet",
"inputSchema": {
"properties": {},
"required": [],
"properties": {
"id": {
"description": "ID of pet to fetch",
"format": "int64",
"type": "integer",
},
},
"required": [
"id",
],
"type": "object",
},
"name": "find_pet_by_id",
@ -1423,7 +1592,7 @@ Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condime
"inputSchema": {
"properties": {
"id": {
"description": "ID of pet to fetch",
"description": "ID of pet to delete",
"format": "int64",
"type": "integer",
},

Wyświetl plik

@ -175,7 +175,7 @@ export async function getToolsFromOpenAPISpec(
for (const source of paramSources) {
if (params[source]) {
mergeJsonSchemaObjects(pathParamsJsonSchema, params[source], {
mergeJsonSchemaObjects(operationParamsJsonSchema, params[source], {
source,
sources: operationParamsSources,
label: `operation "${operationId}"`

Wyświetl plik

@ -141,6 +141,9 @@ catalogs:
p-all:
specifier: ^5.0.0
version: 5.0.0
p-map:
specifier: ^7.0.3
version: 7.0.3
parse-json:
specifier: ^8.3.0
version: 8.3.0
@ -344,6 +347,10 @@ importers:
version: 0.43.1(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.2)(postgres@3.4.7)
apps/e2e:
dependencies:
p-map:
specifier: 'catalog:'
version: 7.0.3
devDependencies:
'@agentic/platform':
specifier: workspace:*
@ -351,6 +358,9 @@ importers:
'@agentic/platform-api-client':
specifier: workspace:*
version: link:../../packages/api-client
'@agentic/platform-core':
specifier: workspace:*
version: link:../../packages/core
'@agentic/platform-fixtures':
specifier: workspace:*
version: link:../../packages/fixtures