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
78 changes: 78 additions & 0 deletions integ-tests/schema-injection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { createTestProject, runCLI } from '../src/test-utils/index.js';
import type { TestProject } from '../src/test-utils/index.js';
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const SCHEMA_URL_PATTERN = /^https:\/\/schema\.agentcore\.aws\.dev\/.+\.json$/;
async function readRawConfig(projectPath: string): Promise<Record<string, unknown>> {
const raw = await readFile(join(projectPath, 'agentcore', 'agentcore.json'), 'utf-8');
return JSON.parse(raw) as Record<string, unknown>;
}

describe('integration: $schema injection in agentcore.json', () => {
let project: TestProject;

beforeAll(async () => {
project = await createTestProject({
language: 'Python',
framework: 'Strands',
modelProvider: 'Bedrock',
memory: 'none',
});
});

afterAll(async () => {
await project.cleanup();
});

it('new project has $schema set to the official URL as the first key', async () => {
const config = await readRawConfig(project.projectPath);
expect(config.$schema).toMatch(SCHEMA_URL_PATTERN);
expect(Object.keys(config)[0]).toBe('$schema');
});

it('$schema persists after adding a resource', async () => {
const memName = `SchemaMem${Date.now().toString().slice(-6)}`;
await runCLI(['add', 'memory', '--name', memName, '--json'], project.projectPath);

const config = await readRawConfig(project.projectPath);
expect(config.$schema).toMatch(SCHEMA_URL_PATTERN);

await runCLI(['remove', 'memory', '--name', memName, '--json'], project.projectPath);
});

it('does not overwrite a custom $schema value', async () => {
const configPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const config = await readRawConfig(project.projectPath);
const customUrl = 'https://example.com/custom-schema.json';
config.$schema = customUrl;
await writeFile(configPath, JSON.stringify(config, null, 2));

const memName = `CustomMem${Date.now().toString().slice(-6)}`;
await runCLI(['add', 'memory', '--name', memName, '--json'], project.projectPath);

const updated = await readRawConfig(project.projectPath);
expect(updated.$schema).toBe(customUrl);

await runCLI(['remove', 'memory', '--name', memName, '--json'], project.projectPath);
});

it('does not inject $schema into a pre-existing project that lacks one', async () => {
const configPath = join(project.projectPath, 'agentcore', 'agentcore.json');
const config = await readRawConfig(project.projectPath);

// Simulate an old project by stripping $schema
delete config.$schema;
await writeFile(configPath, JSON.stringify(config, null, 2));

// Trigger a write
const memName = `OldProj${Date.now().toString().slice(-6)}`;
await runCLI(['add', 'memory', '--name', memName, '--json'], project.projectPath);

const updated = await readRawConfig(project.projectPath);
expect(updated.$schema).toBeUndefined();

await runCLI(['remove', 'memory', '--name', memName, '--json'], project.projectPath);
});
});
161 changes: 136 additions & 25 deletions schemas/agentcore.schema.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"name": {
"type": "string",
"minLength": 1,
Expand All @@ -13,6 +16,11 @@
"minimum": 1,
"maximum": 9007199254740991
},
"managedBy": {
"default": "CDK",
"type": "string",
"enum": ["CDK"]
},
"tags": {
"type": "object",
"propertyNames": {
Expand Down Expand Up @@ -124,7 +132,6 @@
"type": "boolean"
}
},
"required": ["enableOtel"],
"additionalProperties": false
},
"modelProvider": {
Expand All @@ -142,6 +149,103 @@
"type": "string"
}
},
"authorizerType": {
"type": "string",
"enum": ["AWS_IAM", "CUSTOM_JWT"]
},
"authorizerConfiguration": {
"type": "object",
"properties": {
"customJwtAuthorizer": {
"type": "object",
"properties": {
"discoveryUrl": {
"type": "string",
"format": "uri"
},
"allowedAudience": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"allowedClients": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"allowedScopes": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"customClaims": {
"minItems": 1,
"type": "array",
"items": {
"type": "object",
"properties": {
"inboundTokenClaimName": {
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[A-Za-z0-9_.:-]+$"
},
"inboundTokenClaimValueType": {
"type": "string",
"enum": ["STRING", "STRING_ARRAY"]
},
"authorizingClaimMatchValue": {
"type": "object",
"properties": {
"claimMatchOperator": {
"type": "string",
"enum": ["EQUALS", "CONTAINS", "CONTAINS_ANY"]
},
"claimMatchValue": {
"type": "object",
"properties": {
"matchValueString": {
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[A-Za-z0-9_.-]+$"
},
"matchValueStringList": {
"minItems": 1,
"maxItems": 255,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[A-Za-z0-9_.-]+$"
}
}
},
"additionalProperties": false
}
},
"required": ["claimMatchOperator", "claimMatchValue"],
"additionalProperties": false
}
},
"required": ["inboundTokenClaimName", "inboundTokenClaimValueType", "authorizingClaimMatchValue"],
"additionalProperties": false
}
}
},
"required": ["discoveryUrl"],
"additionalProperties": false
}
},
"additionalProperties": false
},
"tags": {
"type": "object",
"propertyNames": {
Expand All @@ -155,6 +259,22 @@
"maxLength": 256,
"pattern": "^[\\p{L}\\p{N}\\s_.:/=+\\-@]*$"
}
},
"lifecycleConfiguration": {
"type": "object",
"properties": {
"idleRuntimeSessionTimeout": {
"type": "integer",
"minimum": 60,
"maximum": 28800
},
"maxLifetime": {
"type": "integer",
"minimum": 60,
"maximum": 28800
}
},
"additionalProperties": false
}
},
"required": ["type", "name", "build", "entrypoint", "codeLocation", "runtimeVersion"],
Expand Down Expand Up @@ -190,7 +310,7 @@
"properties": {
"type": {
"type": "string",
"enum": ["SEMANTIC", "SUMMARIZATION", "USER_PREFERENCE"]
"enum": ["SEMANTIC", "SUMMARIZATION", "USER_PREFERENCE", "EPISODIC"]
},
"name": {
"type": "string",
Expand All @@ -206,6 +326,12 @@
"items": {
"type": "string"
}
},
"reflectionNamespaces": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["type"],
Expand All @@ -227,7 +353,7 @@
}
}
},
"required": ["type", "name", "eventExpiryDuration", "strategies"],
"required": ["type", "name", "eventExpiryDuration"],
"additionalProperties": false
}
},
Expand Down Expand Up @@ -288,7 +414,7 @@
"enum": ["inbound", "outbound"]
}
},
"required": ["type", "name", "discoveryUrl", "vendor"],
"required": ["type", "name"],
"additionalProperties": false
}
]
Expand Down Expand Up @@ -645,7 +771,6 @@
"type": "boolean"
}
},
"required": ["enableOtel"],
"additionalProperties": false
},
"networkMode": {
Expand All @@ -657,14 +782,7 @@
"type": "string"
}
},
"required": [
"artifact",
"pythonVersion",
"name",
"entrypoint",
"codeLocation",
"networkMode"
],
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation"],
"additionalProperties": false
},
"iamPolicy": {
Expand Down Expand Up @@ -710,7 +828,6 @@
}
}
},
"required": ["type"],
"additionalProperties": false
},
"apiGateway": {
Expand Down Expand Up @@ -985,7 +1102,7 @@
}
}
},
"required": ["name", "targets", "authorizerType", "enableSemanticSearch", "exceptionLevel"],
"required": ["name", "targets"],
"additionalProperties": false
}
},
Expand Down Expand Up @@ -1084,7 +1201,6 @@
"type": "boolean"
}
},
"required": ["enableOtel"],
"additionalProperties": false
},
"networkMode": {
Expand All @@ -1096,7 +1212,7 @@
"type": "string"
}
},
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation", "networkMode"],
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation"],
"additionalProperties": false
},
"iamPolicy": {
Expand Down Expand Up @@ -1308,7 +1424,6 @@
"type": "boolean"
}
},
"required": ["enableOtel"],
"additionalProperties": false
},
"networkMode": {
Expand All @@ -1320,7 +1435,7 @@
"type": "string"
}
},
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation", "networkMode"],
"required": ["artifact", "pythonVersion", "name", "entrypoint", "codeLocation"],
"additionalProperties": false
},
"iamPolicy": {
Expand Down Expand Up @@ -1366,7 +1481,6 @@
}
}
},
"required": ["type"],
"additionalProperties": false
},
"apiGateway": {
Expand Down Expand Up @@ -1568,17 +1682,14 @@
"enum": ["FAIL_ON_ANY_FINDINGS", "IGNORE_ALL_FINDINGS"]
}
},
"required": ["name", "statement", "validationMode"],
"required": ["name", "statement"],
"additionalProperties": false
}
}
},
"required": ["name", "policies"],
"required": ["name"],
"additionalProperties": false
}
},
"$schema": {
"type": "string"
}
},
"required": ["name", "version"],
Expand Down
Loading
Loading