fix: comprehensive code quality, theming, and test coverage improvements
- Fix ExemptionManager apiVersion bug (apps/batch resources used wrong API path) - Replace resource: any with proper KubeResource interface (strict TypeScript) - Replace all var(--mui-palette-*) CSS variables with useTheme() + theme.palette.* - Replace custom drawer with MUI Drawer component (proper a11y and theming) - Replace alert() calls with StatusLabel-based inline feedback - Add PolarisErrorBoundary wrapping all registered plugin components - Export getPolarisApiPath/isFullUrl from polaris.ts, deduplicate in PolarisSettings - Fix PolarisDataContext test mock missing triggerRefresh - Fix DashboardView test SimpleTable mock using any - Remove dead NamespaceDetailView (replaced by drawer), unused MockPolarisProvider, unused getSeverityColor export - Add tests for InlineAuditSection, AppBarScoreBadge, topIssues, checkMapping (32 new) - Update CLAUDE.md, CHANGELOG.md, README.md for v0.6.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ vi.mock('./polaris', async importOriginal => {
|
||||
data: makeAuditData([makeResult()]),
|
||||
loading: false,
|
||||
error: null,
|
||||
triggerRefresh: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
@@ -44,5 +45,6 @@ describe('usePolarisDataContext', () => {
|
||||
expect(result.current.data).not.toBeNull();
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(result.current.refresh).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
CHECK_MAPPING,
|
||||
getCheckCategory,
|
||||
getCheckDescription,
|
||||
getCheckName,
|
||||
getSeverityStatus,
|
||||
} from './checkMapping';
|
||||
|
||||
describe('checkMapping', () => {
|
||||
describe('getCheckName', () => {
|
||||
it('returns human-readable name for known check IDs', () => {
|
||||
expect(getCheckName('hostIPCSet')).toBe('Host IPC');
|
||||
expect(getCheckName('cpuRequestsMissing')).toBe('CPU Requests');
|
||||
expect(getCheckName('readinessProbeMissing')).toBe('Readiness Probe');
|
||||
});
|
||||
|
||||
it('returns the raw check ID for unknown checks', () => {
|
||||
expect(getCheckName('unknownCheck')).toBe('unknownCheck');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCheckDescription', () => {
|
||||
it('returns description for known checks', () => {
|
||||
expect(getCheckDescription('hostIPCSet')).toBe('Host IPC should not be configured');
|
||||
});
|
||||
|
||||
it('returns "Unknown check" for unknown checks', () => {
|
||||
expect(getCheckDescription('unknownCheck')).toBe('Unknown check');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCheckCategory', () => {
|
||||
it('returns correct category for each type', () => {
|
||||
expect(getCheckCategory('hostIPCSet')).toBe('Security');
|
||||
expect(getCheckCategory('cpuRequestsMissing')).toBe('Efficiency');
|
||||
expect(getCheckCategory('readinessProbeMissing')).toBe('Reliability');
|
||||
});
|
||||
|
||||
it('defaults to Security for unknown checks', () => {
|
||||
expect(getCheckCategory('unknownCheck')).toBe('Security');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSeverityStatus', () => {
|
||||
it('maps danger to error', () => {
|
||||
expect(getSeverityStatus('danger')).toBe('error');
|
||||
});
|
||||
|
||||
it('maps warning to warning', () => {
|
||||
expect(getSeverityStatus('warning')).toBe('warning');
|
||||
});
|
||||
|
||||
it('defaults to success for other values', () => {
|
||||
expect(getSeverityStatus('ignore')).toBe('success');
|
||||
expect(getSeverityStatus('unknown')).toBe('success');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CHECK_MAPPING', () => {
|
||||
it('has entries for all expected categories', () => {
|
||||
const categories = new Set(Object.values(CHECK_MAPPING).map(c => c.category));
|
||||
expect(categories).toContain('Security');
|
||||
expect(categories).toContain('Efficiency');
|
||||
expect(categories).toContain('Reliability');
|
||||
});
|
||||
|
||||
it('all entries have required fields', () => {
|
||||
for (const [id, info] of Object.entries(CHECK_MAPPING)) {
|
||||
expect(info.name, `${id} missing name`).toBeTruthy();
|
||||
expect(info.description, `${id} missing description`).toBeTruthy();
|
||||
expect(['Security', 'Efficiency', 'Reliability']).toContain(info.category);
|
||||
expect(['danger', 'warning', 'ignore']).toContain(info.defaultSeverity);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -207,22 +207,6 @@ export function getCheckCategory(checkId: string): 'Security' | 'Efficiency' | '
|
||||
return CHECK_MAPPING[checkId]?.category || 'Security';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for severity
|
||||
*/
|
||||
export function getSeverityColor(severity: string): string {
|
||||
switch (severity) {
|
||||
case 'danger':
|
||||
return '#f44336';
|
||||
case 'warning':
|
||||
return '#ff9800';
|
||||
case 'ignore':
|
||||
return '#9e9e9e';
|
||||
default:
|
||||
return '#9e9e9e';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status for StatusLabel component
|
||||
*/
|
||||
|
||||
+2
-2
@@ -300,7 +300,7 @@ export function computeScore(counts: ResultCounts): number {
|
||||
*
|
||||
* @returns Full path to results.json endpoint
|
||||
*/
|
||||
function getPolarisApiPath(): string {
|
||||
export function getPolarisApiPath(): string {
|
||||
const baseUrl = getDashboardUrl();
|
||||
return baseUrl.endsWith('/') ? `${baseUrl}results.json` : `${baseUrl}/results.json`;
|
||||
}
|
||||
@@ -311,7 +311,7 @@ function getPolarisApiPath(): string {
|
||||
* @param url - URL to check
|
||||
* @returns true if full URL, false if relative path
|
||||
*/
|
||||
function isFullUrl(url: string): boolean {
|
||||
export function isFullUrl(url: string): boolean {
|
||||
return url.startsWith('http://') || url.startsWith('https://');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { makeAuditData, makeResult } from '../test-utils';
|
||||
import { getTopIssues } from './topIssues';
|
||||
|
||||
describe('getTopIssues', () => {
|
||||
it('returns empty array when no results', () => {
|
||||
const data = makeAuditData([]);
|
||||
expect(getTopIssues(data)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array when all checks pass', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
expect(getTopIssues(data)).toEqual([]);
|
||||
});
|
||||
|
||||
it('aggregates failing checks from controller-level results', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: 'missing',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const issues = getTopIssues(data);
|
||||
expect(issues).toHaveLength(1);
|
||||
expect(issues[0].checkId).toBe('cpuRequestsMissing');
|
||||
expect(issues[0].checkName).toBe('CPU Requests');
|
||||
expect(issues[0].severity).toBe('warning');
|
||||
expect(issues[0].count).toBe(1);
|
||||
});
|
||||
|
||||
it('aggregates failing checks from pod and container results', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {},
|
||||
PodResult: {
|
||||
Name: 'pod-1',
|
||||
Results: {
|
||||
hostIPCSet: {
|
||||
ID: 'hostIPCSet',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'Security',
|
||||
},
|
||||
},
|
||||
ContainerResults: [
|
||||
{
|
||||
Name: 'container-1',
|
||||
Results: {
|
||||
cpuLimitsMissing: {
|
||||
ID: 'cpuLimitsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const issues = getTopIssues(data);
|
||||
expect(issues).toHaveLength(2);
|
||||
// Danger first
|
||||
expect(issues[0].checkId).toBe('hostIPCSet');
|
||||
expect(issues[0].severity).toBe('danger');
|
||||
expect(issues[1].checkId).toBe('cpuLimitsMissing');
|
||||
});
|
||||
|
||||
it('counts same check across multiple workloads', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-1',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
}),
|
||||
makeResult({
|
||||
Name: 'deploy-2',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const issues = getTopIssues(data);
|
||||
expect(issues).toHaveLength(1);
|
||||
expect(issues[0].count).toBe(2);
|
||||
});
|
||||
|
||||
it('ignores checks with severity "ignore"', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'ignore',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
expect(getTopIssues(data)).toEqual([]);
|
||||
});
|
||||
|
||||
it('sorts danger before warning, then by count descending', () => {
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Name: 'deploy-1',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
}),
|
||||
makeResult({
|
||||
Name: 'deploy-2',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
hostIPCSet: {
|
||||
ID: 'hostIPCSet',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'Security',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const issues = getTopIssues(data);
|
||||
// Danger first regardless of count
|
||||
expect(issues[0].severity).toBe('danger');
|
||||
expect(issues[1].severity).toBe('warning');
|
||||
expect(issues[1].count).toBe(2);
|
||||
});
|
||||
|
||||
it('returns at most 10 issues', () => {
|
||||
const results: Record<
|
||||
string,
|
||||
{
|
||||
ID: string;
|
||||
Message: string;
|
||||
Details: string[];
|
||||
Success: boolean;
|
||||
Severity: 'warning';
|
||||
Category: string;
|
||||
}
|
||||
> = {};
|
||||
for (let i = 0; i < 15; i++) {
|
||||
results[`check${i}`] = {
|
||||
ID: `check${i}`,
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
};
|
||||
}
|
||||
const data = makeAuditData([makeResult({ Results: results })]);
|
||||
expect(getTopIssues(data)).toHaveLength(10);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user