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
7 changes: 4 additions & 3 deletions packages/spacecat-shared-utils/src/strategy-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ const strategy = z.object({
id: nonEmptyString,
name: nonEmptyString,
status: workflowStatus,
url: z.string(),
url: z.union([z.string(), z.array(z.string())]),
description: z.string(),
topic: z.string(),
topicId: z.uuid().optional(),
topic: z.union([z.string(), z.array(z.string())]),
topicId: z.union([z.uuid(), z.array(z.uuid())]).optional(),
metadata: z.record(z.string(), z.any()).optional(),
selectedPrompts: z.array(strategyPromptSelection).optional(),
platform: nonEmptyString.optional(),
opportunities: z.array(strategyOpportunity),
Expand Down
84 changes: 84 additions & 0 deletions packages/spacecat-shared-utils/test/llmo-strategy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,49 @@ describe('llmo-strategy utilities', () => {
expect(result.data).deep.equals(emptyData);
expect(result.exists).equals(true);
});

it('parses strategy with array url, topic, topicId and metadata (end-to-end read)', async () => {
const strategyDataWithArraysAndMetadata = {
opportunities: [
{
id: 'opp-1',
name: 'Improve Page Speed',
description: 'Optimize loading times',
category: 'performance',
},
],
strategies: [
{
id: 'strat-2',
name: 'Multi-URL Strategy',
status: 'planning',
url: ['/strategies/url-a', '/strategies/url-b'],
description: 'Strategy with array fields',
topic: ['Performance', 'SEO'],
topicId: ['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8'],
metadata: { source: 'import', priority: 1 },
platform: 'chatgpt-paid',
createdAt: '2025-01-20T12:00:00Z',
opportunities: [
{
opportunityId: 'opp-1',
status: 'new',
assignee: 'user@example.com',
},
],
},
],
};
const body = {
transformToString: sinon.stub().resolves(JSON.stringify(strategyDataWithArraysAndMetadata)),
};
s3Client.send.resolves({ Body: body });

const result = await readStrategy(siteId, s3Client);

expect(result.data).deep.equals(strategyDataWithArraysAndMetadata);
expect(result.exists).equals(true);
});
});

describe('writeStrategy', () => {
Expand Down Expand Up @@ -253,5 +296,46 @@ describe('llmo-strategy utilities', () => {
const command = s3Client.send.firstCall.args[0];
expect(command.input.Body).equals(JSON.stringify(arbitraryData, null, 2));
});

it('writes strategy with array url, topic, topicId and metadata (end-to-end write)', async () => {
const strategyDataWithArraysAndMetadata = {
opportunities: [
{
id: 'opp-1',
name: 'Improve Page Speed',
description: 'Optimize loading times',
category: 'performance',
},
],
strategies: [
{
id: 'strat-2',
name: 'Multi-URL Strategy',
status: 'planning',
url: ['/strategies/url-a', '/strategies/url-b'],
description: 'Strategy with array fields',
topic: ['Performance', 'SEO'],
topicId: ['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8'],
metadata: { source: 'import', priority: 1 },
platform: 'chatgpt-paid',
createdAt: '2025-01-20T12:00:00Z',
opportunities: [
{
opportunityId: 'opp-1',
status: 'new',
assignee: 'user@example.com',
},
],
},
],
};
s3Client.send.resolves({ VersionId: 'v4' });

const result = await writeStrategy(siteId, strategyDataWithArraysAndMetadata, s3Client);

expect(result).deep.equals({ version: 'v4' });
const command = s3Client.send.firstCall.args[0];
expect(command.input.Body).equals(JSON.stringify(strategyDataWithArraysAndMetadata, null, 2));
});
});
});
114 changes: 114 additions & 0 deletions packages/spacecat-shared-utils/test/strategy-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,120 @@ describe('strategyWorkspaceData', () => {
});
});

describe('url, topic, topicId, and metadata variants', () => {
it('validates strategy with url as an array of strings', () => {
const data = {
...baseWorkspaceData,
strategies: [
{
...baseWorkspaceData.strategies[0],
url: ['/strategies/q1-optimization', '/strategies/q1-alt'],
},
],
};

const result = strategyWorkspaceData.safeParse(data);
expect(result.success).true;
if (result.success) {
expect(result.data.strategies[0].url).deep.equals(['/strategies/q1-optimization', '/strategies/q1-alt']);
}
});

it('validates strategy with topic as an array of strings', () => {
const data = {
...baseWorkspaceData,
strategies: [
{
...baseWorkspaceData.strategies[0],
topic: ['Performance', 'SEO'],
},
],
};

const result = strategyWorkspaceData.safeParse(data);
expect(result.success).true;
if (result.success) {
expect(result.data.strategies[0].topic).deep.equals(['Performance', 'SEO']);
}
});

it('validates strategy with topicId as an array of UUIDs', () => {
const data = {
...baseWorkspaceData,
strategies: [
{
...baseWorkspaceData.strategies[0],
topicId: ['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8'],
},
],
};

const result = strategyWorkspaceData.safeParse(data);
expect(result.success).true;
if (result.success) {
expect(result.data.strategies[0].topicId).deep.equals(['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8']);
}
});

it('validates strategy with metadata object containing arbitrary key-value pairs', () => {
const data = {
...baseWorkspaceData,
strategies: [
{
...baseWorkspaceData.strategies[0],
metadata: {
source: 'import',
priority: 1,
tags: ['q1', 'optimization'],
nested: { key: 'value' },
},
},
],
};

const result = strategyWorkspaceData.safeParse(data);
expect(result.success).true;
if (result.success) {
expect(result.data.strategies[0].metadata).deep.equals({
source: 'import',
priority: 1,
tags: ['q1', 'optimization'],
nested: { key: 'value' },
});
}
});

it('validates strategy with single-value url, topic, topicId (backward compatibility)', () => {
const data = {
...baseWorkspaceData,
strategies: [
{
...baseWorkspaceData.strategies[0],
url: '/strategies/q1-optimization',
topic: 'Performance',
topicId: '550e8400-e29b-41d4-a716-446655440000',
},
],
};

const result = strategyWorkspaceData.safeParse(data);
expect(result.success).true;
if (result.success) {
expect(result.data.strategies[0].url).equals('/strategies/q1-optimization');
expect(result.data.strategies[0].topic).equals('Performance');
expect(result.data.strategies[0].topicId).equals('550e8400-e29b-41d4-a716-446655440000');
}
});

it('validates strategy without metadata (backward compatibility)', () => {
const result = strategyWorkspaceData.safeParse(baseWorkspaceData);
expect(result.success).true;
if (result.success) {
expect(result.data.strategies[0].metadata).to.be.undefined;
}
});
});

describe('required fields validation', () => {
it('fails when opportunity is missing required fields', () => {
const data = {
Expand Down
Loading