Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit test for the components #43

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ Based on [Pastel](https://github.com/vadimdemedes/create-pastel-app)
- run `npm install`
- run `npx tsx ./source/cli.tsx`

### Writing Tests
Permit CLI uses [`vitest`](https://vitest.dev/) as a tool for writing tests. It also uses [`ink-testing-library`](https://github.com/vadimdemedes/ink-testing-library) to render the `Ink` components.

- run `npx vitest` for testing
- run `npx vitest --coverage` for code coverage.


## CLI

```
Expand Down
2 changes: 1 addition & 1 deletion source/lib/gitops/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async function configurePermit(
status: gitConfigResponse.status,
};
} else {
throw new Error('Invalid Configuration ' + response);
throw new Error('Invalid Configuration ');
}
}

Expand Down
49 changes: 49 additions & 0 deletions tests/OPAPolicy.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render } from 'ink-testing-library';
import Policy from '../source/commands/opa/policy';
import delay from 'delay';
global.fetch = vi.fn();
const enter = '\r';

describe('OPA Policy Command', () => {
it('should render the policy command', async () => {
const options = {
serverUrl: 'http://localhost:8181',
keyAccount: 'testAccount',
apiKey: 'permit_key_'.concat('a'.repeat(97)),
};
(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => ({
result: [
{ id: 'policy1', name: 'policyName1' },
{ id: 'policy2', name: 'policyName2' },
],
}),
status: 200,
});
const { stdin, lastFrame } = render(<Policy options={options} />);
expect(lastFrame()?.toString()).toMatch(
'Listing Policies on Opa Server=http://localhost:8181',
);
await delay(50);
expect(lastFrame()?.toString()).toMatch('Showing 2 of 2 policies:');
expect(lastFrame()?.toString()).toMatch('policy1');
expect(lastFrame()?.toString()).toMatch('policy2');
stdin.write(enter);
});
it('should render the policy command with error', async () => {
const options = {
serverUrl: 'http://localhost:8181',
keyAccount: 'testAccount',
};
(fetch as any).mockRejectedValueOnce(new Error('Error'));
const { lastFrame } = render(<Policy options={options} />);
expect(lastFrame()?.toString()).toMatch(
'Listing Policies on Opa Server=http://localhost:8181',
);
await delay(50);
expect(lastFrame()?.toString()).toMatch(/Request failed:/);
});
});
94 changes: 94 additions & 0 deletions tests/PDPCheck.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import { render } from 'ink-testing-library';
import { describe, vi, it, expect, afterEach } from 'vitest';
import delay from 'delay';
import Check from '../source/commands/pdp/check';

global.fetch = vi.fn();

describe('PDP Check Component', () => {
afterEach(() => {
// Clear mock calls after each test
vi.clearAllMocks();
});
it('should render with the given options', async () => {
(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => ({ allow: true }),
});
const options = {
user: 'testUser',
resource: 'testResource',
action: 'testAction',
tenant: 'testTenant',
keyAccount: 'testKeyAccount',
};

const { lastFrame } = render(<Check options={options} />);
expect(lastFrame()).toMatchInlineSnapshot(`
"Checking user="testUser" action=testAction resource=testResource at tenant=testTenant"
`);
await delay(50);
expect(lastFrame()?.toString()).toContain('ALLOWED');
});
it('should render with the given options', async () => {
(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => ({ allow: false }),
});
const options = {
user: 'testUser',
resource: 'testResource',
action: 'testAction',
tenant: 'testTenant',
keyAccount: 'testKeyAccount',
};

const { lastFrame } = render(<Check options={options} />);
expect(lastFrame()).toMatchInlineSnapshot(`
"Checking user="testUser" action=testAction resource=testResource at tenant=testTenant"
`);
await delay(50);
expect(lastFrame()?.toString()).toContain('DENIED');
});
it('should render with the given options', async () => {
(fetch as any).mockResolvedValueOnce({
ok: false,
text: async () => 'Error',
});
const options = {
user: 'testUser',
resource: 'testResource',
action: 'testAction',
tenant: 'testTenant',
keyAccount: 'testKeyAccount',
};

const { lastFrame } = render(<Check options={options} />);
expect(lastFrame()).toMatchInlineSnapshot(`
"Checking user="testUser" action=testAction resource=testResource at tenant=testTenant"
`);
await delay(50);
expect(lastFrame()?.toString()).toContain('Error');
});
it('should render with the given options with multiple resource', async () => {
(fetch as any).mockResolvedValueOnce({
ok: true,
json: async () => ({ allow: true }),
});
const options = {
user: 'testUser',
resource: 'testResourceType: testRecsourceKey',
action: 'testAction',
tenant: 'testTenant',
keyAccount: 'testKeyAccount',
};

const { lastFrame } = render(<Check options={options} />);
expect(lastFrame()).toMatchInlineSnapshot(`
"Checking user="testUser" action=testAction resource=testResourceType: testRecsourceKey at
tenant=testTenant"`);
await delay(50);
expect(lastFrame()?.toString()).toContain('ALLOWED');
});
});
11 changes: 11 additions & 0 deletions tests/PDPRun.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { describe, expect, it } from 'vitest';
import { render } from 'ink-testing-library';
import Run from '../source/commands/pdp/run';

describe('PDP Run', () => {
it('Should render the PDP Run command', () => {
const { lastFrame } = render(<Run options={{ opa: 8181 }} />);
expect(lastFrame()?.toString()).toMatch(/Loading Token/);
});
});
44 changes: 44 additions & 0 deletions tests/apiKey.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { render } from 'ink-testing-library';
import ApiKey from '../source/commands/apiKey';
import { describe, it, expect } from 'vitest';
import delay from 'delay';

describe('ApiKey', () => {
it('Should save the key', () => {
const permitKey = 'permit_key_'.concat('a'.repeat(97));
const { lastFrame } = render(
<ApiKey args={['save', permitKey]} options={{ keyAccount: 'test' }} />,
);
expect(lastFrame()).toMatch(/Key saved to secure key store./);
});
it('Should validate the key', () => {
const permitKey = 'permit_key_'.concat('a'.repeat(97));
const { lastFrame } = render(
<ApiKey
args={['validate', permitKey]}
options={{ keyAccount: 'test' }}
/>,
);
expect(lastFrame()).toMatch(/Key is valid./);
});
it('Should read the key', async () => {
const permitKey = 'permit_key_'.concat('a'.repeat(97));
const { lastFrame } = render(
<ApiKey args={['read', permitKey]} options={{ keyAccount: 'test' }} />,
);
await delay(50);
expect(lastFrame()).toMatch(/permit_key_aaaaaaa/);
});
it('Invalid Key', async () => {
const permitKey = 'permit_key'.concat('a'.repeat(97));
const { lastFrame } = render(
<ApiKey
args={['validate', permitKey]}
options={{ keyAccount: 'test' }}
/>,
);
await delay(50);
expect(lastFrame()).toMatch(/Key is not valid./);
});
});
86 changes: 86 additions & 0 deletions tests/components/AuthProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react';
import { render } from 'ink-testing-library';
import { AuthProvider, useAuth } from '../../source/components/AuthProvider.js';
import { loadAuthToken } from '../../source/lib/auth.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Text } from 'ink';
import delay from 'delay';

vi.mock('../../source/lib/auth.js', () => ({
loadAuthToken: vi.fn(),
}));

describe('AuthProvider', () => {
it('should display loading text while loading token', async () => {
(loadAuthToken as any).mockResolvedValueOnce(new Promise(() => {}));

const { lastFrame } = render(
<AuthProvider>
<Text>Child Component</Text>
</AuthProvider>,
);

expect(lastFrame()).toContain('Loading Token');
});
it('should display error message if loading token fails', async () => {
(loadAuthToken as any).mockRejectedValueOnce(
new Error('Failed to load token'),
);

const { lastFrame } = render(
<AuthProvider>
<Text>Child Component</Text>
</AuthProvider>,
);

await delay(50);
expect(lastFrame()).toContain('Failed to load token');
});

it('should display children when token is loaded successfully', async () => {
(loadAuthToken as any).mockResolvedValueOnce('mocked-token');

const { lastFrame } = render(
<AuthProvider>
<Text>Child Component</Text>
</AuthProvider>,
);

await delay(50);
expect(lastFrame()).toContain('Child Component');
});
it('should use the auth context successfully', async () => {
const ChildComponent = () => {
const { authToken } = useAuth();
return <Text>{authToken || 'No token'}</Text>;
};

(loadAuthToken as any).mockResolvedValueOnce('mocked-token');

const { lastFrame } = render(
<AuthProvider>
<ChildComponent />
</AuthProvider>,
);

await delay(100);
expect(lastFrame()).toContain('mocked-token');
});

it('should throw an error when useAuth is called outside of AuthProvider', () => {
const ChildComponent = () => {
let apiKey: string;
try {
const { authToken } = useAuth();
apiKey = authToken;
} catch (error) {
return <Text>useAuth must be used within an AuthProvider</Text>;
}
return <Text>{apiKey || 'No token'}</Text>;
};
const { lastFrame } = render(<ChildComponent />);
expect(lastFrame()).toContain(
'useAuth must be used within an AuthProvider',
);
});
});
37 changes: 37 additions & 0 deletions tests/components/PDPCommand.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render } from 'ink-testing-library';
import PDPCommand from '../../source/components/PDPCommand';
import { AuthProvider } from '../../source/components/AuthProvider';
import delay from 'delay';
import { loadAuthToken } from '../../source/lib/auth';
vi.mock('../../source/lib/auth', () => ({
loadAuthToken: vi.fn(),
}));
describe('PDP Component', () => {
it('should render the PDP component with auth token', async () => {
(loadAuthToken as any).mockResolvedValueOnce(
'permit_key_'.concat('a'.repeat(97)),
);
const { lastFrame } = render(
<AuthProvider>
<PDPCommand opa={8181} />
</AuthProvider>,
);
expect(lastFrame()?.toString()).toMatch('Loading Token');
await delay(50);
expect(lastFrame()?.toString()).toMatch(
'Run the following command from your terminal:',
);
});
it('should render the Spinner', async () => {
const { lastFrame } = render(
<AuthProvider>
<PDPCommand opa={8181} />
</AuthProvider>,
);
expect(lastFrame()?.toString()).toMatch('Loading Token');
await delay(50);
expect(lastFrame()?.toString()).toMatch('Loading command');
});
});
Loading