Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,20 @@ jobs:
- 3.12.0
- 3.13.0
- 3.14.1
env:
BACKEND_APISIX_VERSION: ${{ matrix.version }}
BACKEND_APISIX_IMAGE: ${{ matrix.version }}-debian
- 3.15.0
- 3.16.0
- dev
steps:
- name: Determine APISIX image tag
run: |
if [ "${{ matrix.version }}" = "dev" ]; then
echo "BACKEND_APISIX_VERSION=999.999.999" >> $GITHUB_ENV
echo "BACKEND_APISIX_IMAGE=dev" >> $GITHUB_ENV
else
echo "BACKEND_APISIX_VERSION=${{ matrix.version }}" >> $GITHUB_ENV
echo "BACKEND_APISIX_IMAGE=${{ matrix.version }}-debian" >> $GITHUB_ENV
fi

- uses: actions/checkout@v4

# Setup backend environment
Expand Down Expand Up @@ -80,6 +90,8 @@ jobs:
version:
- 3.13.0
- 3.14.1
- 3.15.0
- 3.16.0
- dev
steps:
- name: Determine APISIX image tag
Expand Down
216 changes: 216 additions & 0 deletions libs/backend-apisix-standalone/e2e/validate.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { DifferV3 } from '@api7/adc-differ';
import * as ADCSDK from '@api7/adc-sdk';
import { lastValueFrom } from 'rxjs';
import { gte } from 'semver';

import { BackendAPISIXStandalone } from '../src';
import {
defaultBackendOptions,
server1,
token1,
} from './support/constants';
import { conditionalDescribe, semverCondition } from './support/utils';

const configToEvents = (config: ADCSDK.Configuration): Array<ADCSDK.Event> => {
return DifferV3.diff(
config as ADCSDK.InternalConfiguration,
{} as ADCSDK.InternalConfiguration,
);
};

conditionalDescribe(semverCondition(gte, '3.16.0'))('Validate', () => {
let backend: BackendAPISIXStandalone;

beforeAll(() => {
backend = new BackendAPISIXStandalone({
server: server1,
token: token1,
cacheKey: 'validate-test',
...defaultBackendOptions,
});
});

it('should succeed with empty configuration', async () => {
const result = await lastValueFrom(backend.validate([]));
expect(result.success).toBe(true);
expect(result.errors).toEqual([]);
});
Comment thread
nic-6443 marked this conversation as resolved.

it('should succeed with valid service and route', async () => {
const config: ADCSDK.Configuration = {
services: [
{
name: 'validate-test-svc',
upstream: {
scheme: 'http',
nodes: [{ host: 'httpbin.org', port: 80, weight: 100 }],
},
routes: [
{
name: 'validate-test-route',
uris: ['/validate-test'],
methods: ['GET'],
},
],
},
],
};

const result = await lastValueFrom(
backend.validate(configToEvents(config)),
);
expect(result.success).toBe(true);
expect(result.errors).toEqual([]);
});

it('should succeed with valid consumer', async () => {
const config: ADCSDK.Configuration = {
consumers: [
{
username: 'validate-test-consumer',
plugins: {
'key-auth': { key: 'test-key-123' },
},
},
],
};

const result = await lastValueFrom(
backend.validate(configToEvents(config)),
);
expect(result.success).toBe(true);
expect(result.errors).toEqual([]);
});

it('should fail with invalid plugin configuration', async () => {
const config: ADCSDK.Configuration = {
services: [
{
name: 'validate-bad-plugin-svc',
upstream: {
scheme: 'http',
nodes: [{ host: 'httpbin.org', port: 80, weight: 100 }],
},
routes: [
{
name: 'validate-bad-plugin-route',
uris: ['/bad-plugin'],
plugins: {
'limit-count': {
// missing required fields: count, time_window
},
},
},
],
},
],
};

const result = await lastValueFrom(
backend.validate(configToEvents(config)),
);
expect(result.success).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
expect(result.errors[0].resource_type).toBe('routes');
});

it('should fail with invalid route (bad uri type)', async () => {
const config: ADCSDK.Configuration = {
services: [
{
name: 'validate-bad-route-svc',
upstream: {
scheme: 'http',
nodes: [{ host: 'httpbin.org', port: 80, weight: 100 }],
},
routes: [
{
name: 'validate-bad-route',
uris: [123 as unknown as string],
},
],
},
],
};

const result = await lastValueFrom(
backend.validate(configToEvents(config)),
);
expect(result.success).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
});

it('should collect multiple errors', async () => {
const config: ADCSDK.Configuration = {
services: [
{
name: 'validate-multi-err-svc',
upstream: {
scheme: 'http',
nodes: [{ host: 'httpbin.org', port: 80, weight: 100 }],
},
routes: [
{
name: 'validate-multi-err-route1',
uris: ['/multi-err-1'],
plugins: {
'limit-count': {},
},
},
{
name: 'validate-multi-err-route2',
uris: ['/multi-err-2'],
plugins: {
'limit-count': {},
},
},
],
},
],
};

const result = await lastValueFrom(
backend.validate(configToEvents(config)),
);
expect(result.success).toBe(false);
expect(result.errors.length).toBeGreaterThanOrEqual(2);
});

it('should succeed with mixed resource types', async () => {
const config: ADCSDK.Configuration = {
services: [
{
name: 'validate-mixed-svc',
upstream: {
scheme: 'https',
nodes: [{ host: 'httpbin.org', port: 443, weight: 100 }],
},
routes: [
{
name: 'validate-mixed-route',
uris: ['/mixed-test'],
methods: ['GET', 'POST'],
},
],
},
],
consumers: [
{
username: 'validate-mixed-consumer',
plugins: {
'key-auth': { key: 'mixed-key-456' },
},
},
],
global_rules: {
prometheus: { prefer_name: false },
} as ADCSDK.Configuration['global_rules'],
};

const result = await lastValueFrom(
backend.validate(configToEvents(config)),
);
expect(result.success).toBe(true);
expect(result.errors).toEqual([]);
});
});
1 change: 1 addition & 0 deletions libs/backend-apisix-standalone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"vitest": "catalog:"
},
"dependencies": {
"@api7/adc-backend-apisix": "workspace:*",
"@api7/adc-sdk": "workspace:*",
"axios": "catalog:",
"rxjs": "catalog:",
Expand Down
16 changes: 16 additions & 0 deletions libs/backend-apisix-standalone/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ADCSDK from '@api7/adc-sdk';
import { Validator } from '@api7/adc-backend-apisix';
import axios, { type AxiosInstance } from 'axios';
import { type Observable, Subject, from, map, of, switchMap } from 'rxjs';
import semver, { SemVer, eq as semverEQ } from 'semver';
Expand Down Expand Up @@ -166,6 +167,21 @@ export class BackendAPISIXStandalone implements ADCSDK.Backend {
});
}

public validate(events: Array<ADCSDK.Event>) {
const server = this.serverTokenMap.keys().next().value as string;
const token = this.serverTokenMap.get(server)!;
return from(
new Validator({
client: this.client,
eventSubject: this.subject,
requestConfig: {
baseURL: server,
headers: { 'X-API-KEY': token },
},
}).validate(events),
);
}

supportStreamRoute?: () => Promise<boolean>;

public __TEST_ONLY = {
Expand Down
Loading
Loading