chore: import upstream snapshot with attribution
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ADDITIONAL_PLUGINS,
|
||||
ALL_PLUGINS,
|
||||
BASE_PLUGINS,
|
||||
BIAS_PLUGINS,
|
||||
CONFIG_REQUIRED_PLUGINS,
|
||||
HARM_PLUGINS,
|
||||
PII_PLUGINS,
|
||||
} from '../../../src/redteam/constants';
|
||||
|
||||
describe('Plugin IDs', () => {
|
||||
const findPluginIdAssignments = (fileContent: string): string[] => {
|
||||
// Look for patterns like `id = 'plugin-name'` or `PLUGIN_ID = 'plugin-name'`
|
||||
const idAssignmentRegex = /\b(id|PLUGIN_ID)\s*=\s*['"]([^'"]+)['"]/g;
|
||||
const matches = [];
|
||||
let match;
|
||||
while ((match = idAssignmentRegex.exec(fileContent)) !== null) {
|
||||
matches.push(match[2]);
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
it('should use plugin IDs that match those defined in constants', () => {
|
||||
// Get all plugin implementation files
|
||||
const pluginDir = path.resolve(__dirname, '../../../src/redteam/plugins');
|
||||
const pluginFiles = fs
|
||||
.readdirSync(pluginDir)
|
||||
.filter(
|
||||
(file) =>
|
||||
file.endsWith('.ts') &&
|
||||
!file.endsWith('.d.ts') &&
|
||||
file !== 'index.ts' &&
|
||||
file !== 'base.ts',
|
||||
);
|
||||
|
||||
// Track all plugin IDs used in implementations
|
||||
const usedPluginIds: string[] = [];
|
||||
|
||||
pluginFiles.forEach((file) => {
|
||||
const filePath = path.join(pluginDir, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const ids = findPluginIdAssignments(content);
|
||||
|
||||
if (ids.length > 0) {
|
||||
usedPluginIds.push(...ids);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check subdirectories
|
||||
const subdirectories = fs
|
||||
.readdirSync(pluginDir, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
|
||||
subdirectories.forEach((subdir) => {
|
||||
const subdirPath = path.join(pluginDir, subdir);
|
||||
const subFiles = fs
|
||||
.readdirSync(subdirPath)
|
||||
.filter((file) => file.endsWith('.ts') && !file.endsWith('.d.ts') && file !== 'index.ts');
|
||||
|
||||
subFiles.forEach((file) => {
|
||||
const filePath = path.join(subdirPath, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const ids = findPluginIdAssignments(content);
|
||||
|
||||
if (ids.length > 0) {
|
||||
usedPluginIds.push(...ids);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Filter out duplicates
|
||||
const uniqueIds = [...new Set(usedPluginIds)];
|
||||
|
||||
// Create a comprehensive list of all expected plugin IDs, including the prefixed versions
|
||||
const expectedPrefixedPluginIds = new Set<string>();
|
||||
|
||||
// Add common plugin format - 'promptfoo:redteam:plugin-name'
|
||||
ALL_PLUGINS.forEach((pluginId) => {
|
||||
if (typeof pluginId === 'string' && !pluginId.includes(':')) {
|
||||
expectedPrefixedPluginIds.add(`promptfoo:redteam:${pluginId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Add harm plugins which might have different prefixes
|
||||
Object.keys(HARM_PLUGINS).forEach((harmPlugin) => {
|
||||
if (typeof harmPlugin === 'string') {
|
||||
const fullPluginId = `promptfoo:redteam:${harmPlugin}`;
|
||||
expectedPrefixedPluginIds.add(fullPluginId);
|
||||
}
|
||||
});
|
||||
|
||||
// Add PII plugins with their prefixes
|
||||
PII_PLUGINS.forEach((piiPlugin) => {
|
||||
expectedPrefixedPluginIds.add(`promptfoo:redteam:${piiPlugin}`);
|
||||
});
|
||||
|
||||
// Add special case for general PII plugin
|
||||
expectedPrefixedPluginIds.add('promptfoo:redteam:pii');
|
||||
|
||||
// Add special case for general harmful plugin
|
||||
expectedPrefixedPluginIds.add('promptfoo:redteam:harmful');
|
||||
|
||||
// Add special cases from harm sub-categories
|
||||
// These are handled specially in the constants file as nested objects
|
||||
uniqueIds.forEach((id) => {
|
||||
if (id.startsWith('promptfoo:redteam:harmful:')) {
|
||||
expectedPrefixedPluginIds.add(id);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle special case: 'policy' without prefix
|
||||
uniqueIds.forEach((id) => {
|
||||
if (id === 'policy') {
|
||||
// This is a special case where the ID is not prefixed in the code
|
||||
}
|
||||
});
|
||||
|
||||
// For each plugin ID found in the implementations, check if it matches an expected format
|
||||
const unexpectedPlugins: { id: string; baseId: string }[] = [];
|
||||
const idsMissingPrefix: string[] = [];
|
||||
|
||||
uniqueIds.forEach((id) => {
|
||||
if (!id.startsWith('promptfoo:redteam:') && id !== 'policy') {
|
||||
idsMissingPrefix.push(id);
|
||||
}
|
||||
|
||||
if (!expectedPrefixedPluginIds.has(id) && id !== 'policy') {
|
||||
const baseId = id.replace('promptfoo:redteam:', '');
|
||||
const isHarmSubcategory = baseId.startsWith('harmful:');
|
||||
|
||||
// Special case for harm subcategories
|
||||
if (!isHarmSubcategory) {
|
||||
// Non-harm plugins should match one of the plugin types in constants
|
||||
const allPluginsList = [
|
||||
...BASE_PLUGINS,
|
||||
...ADDITIONAL_PLUGINS,
|
||||
...CONFIG_REQUIRED_PLUGINS,
|
||||
...PII_PLUGINS,
|
||||
...BIAS_PLUGINS,
|
||||
'pii', // Add the general pii plugin
|
||||
'bias', // Add the general bias plugin
|
||||
'harmful', // Add the general harmful plugin
|
||||
...Object.keys(HARM_PLUGINS),
|
||||
];
|
||||
|
||||
const matchesExpectedPlugin = allPluginsList.some((p) => baseId === p);
|
||||
|
||||
if (!matchesExpectedPlugin) {
|
||||
unexpectedPlugins.push({ id, baseId });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Make a single assertion for all unexpected plugins
|
||||
expect(
|
||||
{ unexpectedPlugins, idsMissingPrefix },
|
||||
[
|
||||
'Unexpected plugin IDs:',
|
||||
...unexpectedPlugins.map(({ id, baseId }) => ` - ${id} (base: ${baseId})`),
|
||||
idsMissingPrefix.length > 0 ? 'IDs missing promptfoo:redteam: prefix:' : '',
|
||||
...idsMissingPrefix.map((id) => ` - ${id}`),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
).toEqual({ unexpectedPlugins: [], idsMissingPrefix: [] });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user