Skip to content

Commit 4b37f78

Browse files
committed
feat: Added additional tests for edit, login and refresh
1 parent 5d311dc commit 4b37f78

File tree

4 files changed

+477
-11
lines changed

4 files changed

+477
-11
lines changed

src/orchestrators/login.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,11 @@ const schema = {
2020
required: ['accessToken'],
2121
}
2222

23-
interface Credentials {
24-
accessToken: string;
25-
email: string;
26-
userId: string;
27-
expiry: string;
28-
}
29-
3023
export interface LoginArgs {
3124
username?: string;
3225
password?: string;
3326
}
3427

35-
3628
export class LoginOrchestrator {
3729
static async run(args?: LoginArgs) {
3830
if (args?.username && !args?.password) {
@@ -53,8 +45,14 @@ export class LoginOrchestrator {
5345
}
5446

5547
private static async loginWithCredentials(username: string, password: string) {
56-
const accessToken = await DashboardApiClient.login(username, password);
57-
await LoginHelper.save(accessToken);
48+
try {
49+
const accessToken = await DashboardApiClient.login(username, password);
50+
await LoginHelper.save(accessToken);
51+
} catch (e) {
52+
console.error(chalk.red(JSON.parse(e.message).error));
53+
54+
process.exit(1);
55+
}
5856
}
5957

6058
private static async loginViaBrowser() {
@@ -82,7 +80,7 @@ Manually open it here: ${config.dashboardUrl}/auth/cli`
8280
await new Promise<void>((resolve, reject) => {
8381
app.post('/', async (req, res) => {
8482
try {
85-
const body = req.body as Credentials;
83+
const body = req.body as { accessToken: string };
8684

8785
if (!ajv.validate(schema, body)) {
8886
console.error(chalk.red('Received invalid credentials. Please submit a support ticket'))
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { MockOs } from '../mocks/system.js';
3+
import { MockReporter } from '../mocks/reporter.js';
4+
import { ConnectOrchestrator } from '../../../src/orchestrators/connect';
5+
import * as net from 'node:net';
6+
import { config } from '../../../src/config.js';
7+
import { fakeLogin, fakeLogout } from '../mocks/mock-login';
8+
import * as open from 'open'
9+
import { LoginOrchestrator } from '../../../src/orchestrators/login';
10+
import { Server } from 'node:http';
11+
import { vol } from 'memfs';
12+
import { EditOrchestrator } from '../../../src/orchestrators/edit';
13+
14+
vi.mock(import('../../../src/orchestrators/login'), async () => {
15+
return {
16+
LoginOrchestrator: {
17+
run: async () => {}
18+
},
19+
}
20+
})
21+
22+
vi.mock('node:fs', async () => {
23+
const { fs } = await import('memfs');
24+
return fs
25+
})
26+
27+
vi.mock('node:fs/promises', async () => {
28+
const { fs } = await import('memfs');
29+
return fs.promises;
30+
})
31+
32+
vi.mock(import('open'), async () => {
33+
return {
34+
default: vi.fn()
35+
}
36+
})
37+
38+
// The apply orchestrator directly calls plan so this will test both
39+
describe('Edit orchestrator tests', () => {
40+
beforeEach(() => {
41+
vol.reset();
42+
})
43+
44+
it('It will start a local server on config.connectServerPort', async () => {
45+
const reporter = new MockReporter();
46+
await fakeLogin();
47+
48+
const openSpy = vi.spyOn(open, 'default');
49+
EditOrchestrator.run('codify', reporter);
50+
51+
await expect.poll(async () => {
52+
return checkPortStatus(config.connectServerPort);
53+
}).toBeTruthy()
54+
});
55+
56+
57+
afterEach(() => {
58+
vi.resetAllMocks();
59+
MockOs.reset();
60+
})
61+
62+
function checkPortStatus(port: number, host = '127.0.0.1') {
63+
return new Promise((resolve, reject) => {
64+
const socket = new net.Socket();
65+
66+
socket.once('connect', () => {
67+
// If 'connect' event fires, the port is open and listening
68+
socket.destroy();
69+
resolve(true); // Port is in use
70+
});
71+
72+
socket.once('error', (err) => {
73+
// Any error typically means the port is not listening
74+
// EADDRNOTAVAIL, ECONNREFUSED, etc.
75+
reject(err); // Port is likely free or unreachable
76+
});
77+
78+
socket.once('timeout', () => {
79+
socket.destroy();
80+
reject(new Error('Connection attempt timed out'));
81+
});
82+
83+
socket.connect(port, host);
84+
});
85+
}
86+
87+
})
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { MockOs } from '../mocks/system.js';
3+
import { MockReporter } from '../mocks/reporter.js';
4+
import * as net from 'node:net';
5+
import { config } from '../../../src/config.js';
6+
import { fakeLogout } from '../mocks/mock-login';
7+
import * as open from 'open'
8+
import { LoginOrchestrator } from '../../../src/orchestrators/login';
9+
import { vol } from 'memfs';
10+
import { LoginHelper } from '../../../src/connect/login-helper';
11+
12+
vi.mock('node:fs', async () => {
13+
const { fs } = await import('memfs');
14+
return fs
15+
})
16+
17+
vi.mock('node:fs/promises', async () => {
18+
const { fs } = await import('memfs');
19+
return fs.promises;
20+
})
21+
22+
vi.mock(import('open'), async () => {
23+
return {
24+
default: vi.fn()
25+
}
26+
})
27+
28+
const tempJWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30'
29+
30+
// The apply orchestrator directly calls plan so this will test both
31+
describe('Login orchestrator tests', () => {
32+
beforeEach(() => {
33+
vol.reset();
34+
})
35+
36+
it('It can save a successful login', async () => {
37+
await fakeLogout();
38+
expect(LoginHelper.get().isLoggedIn).to.be.false;
39+
40+
const openSpy = vi.spyOn(open, 'default');
41+
LoginOrchestrator.run();
42+
43+
await expect.poll(async () => {
44+
return checkPortStatus(config.loginServerPort);
45+
}, {}).toBeTruthy()
46+
47+
expect(openSpy).toBeCalledWith('https://dashboard.codifycli.com/auth/cli');
48+
49+
const saveResponse = await fetch(`http://localhost:${config.loginServerPort}`, {
50+
method: 'POST',
51+
headers: { 'Content-Type': 'application/json' },
52+
body: JSON.stringify({
53+
accessToken: tempJWT,
54+
})
55+
})
56+
57+
expect(saveResponse.ok).to.be.true;
58+
expect(LoginHelper.get().isLoggedIn).to.be.true;
59+
expect(LoginHelper.get().credentials).toMatchObject({
60+
accessToken: tempJWT,
61+
})
62+
63+
expect(() => checkPortStatus(config.loginServerPort)).to.throw
64+
});
65+
66+
it('It will login via credentials (wrong credentials)', async () => {
67+
await fakeLogout();
68+
expect(LoginHelper.get().isLoggedIn).to.be.false;
69+
70+
expect(async () => await LoginOrchestrator.run({
71+
username: 'my-user',
72+
password: 'password'
73+
})).to.throw;
74+
75+
expect(LoginHelper.get().isLoggedIn).to.be.false;
76+
});
77+
78+
it('It will login via credentials (correct credentials)', async () => {
79+
await fakeLogout();
80+
expect(LoginHelper.get().isLoggedIn).to.be.false;
81+
82+
global.fetch = vi.fn(() => Promise.resolve({
83+
ok: true,
84+
json: () => Promise.resolve({ accessToken: tempJWT })
85+
}))
86+
87+
const fetchSpy = vi.spyOn(global, 'fetch');
88+
89+
await LoginOrchestrator.run({
90+
username: 'my-user',
91+
password: 'password'
92+
})
93+
94+
expect(fetchSpy).toHaveBeenCalledWith(`${config.dashboardUrl}/api/v1/auth/cli`,
95+
{
96+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
97+
email: 'my-user',
98+
password: 'password',
99+
})
100+
})
101+
expect(LoginHelper.get().isLoggedIn).to.be.true;
102+
expect(LoginHelper.get().credentials).toMatchObject({
103+
accessToken: tempJWT,
104+
})
105+
});
106+
107+
108+
afterEach(() => {
109+
vi.resetAllMocks();
110+
MockOs.reset();
111+
})
112+
113+
function checkPortStatus(port: number, host = '127.0.0.1') {
114+
return new Promise((resolve, reject) => {
115+
const socket = new net.Socket();
116+
117+
socket.once('connect', () => {
118+
// If 'connect' event fires, the port is open and listening
119+
socket.destroy();
120+
resolve(true); // Port is in use
121+
});
122+
123+
socket.once('error', (err) => {
124+
// Any error typically means the port is not listening
125+
// EADDRNOTAVAIL, ECONNREFUSED, etc.
126+
reject(err); // Port is likely free or unreachable
127+
});
128+
129+
socket.once('timeout', () => {
130+
socket.destroy();
131+
reject(new Error('Connection attempt timed out'));
132+
});
133+
134+
socket.connect(port, host);
135+
});
136+
}
137+
138+
})

0 commit comments

Comments
 (0)