Skip to content
This repository was archived by the owner on Oct 7, 2025. It is now read-only.

Commit 409f649

Browse files
committed
chore: implement read via config rules
1 parent f7e5439 commit 409f649

File tree

2 files changed

+102
-79
lines changed

2 files changed

+102
-79
lines changed

src/providers/config-provider.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
IRuleDefinition,
66
} from 'lightning-flow-scanner-core';
77
import * as vsce from 'vscode';
8-
98
import { Document, parse } from 'yaml';
109

1110
type Configuration = {
@@ -14,10 +13,7 @@ type Configuration = {
1413
};
1514

1615
export class ConfigProvider {
17-
public async discover(configPath: string): Promise<{
18-
fspath: string;
19-
config: unknown;
20-
}> {
16+
public async discover(configPath: string): Promise<Configuration> {
2117
const configurationName = 'flow-scanner';
2218

2319
const findInJson = [`.${configPath}.json`, `${configurationName}.json`];
@@ -51,10 +47,7 @@ export class ConfigProvider {
5147
private async writeConfigFile(
5248
configurationName: string,
5349
configPath: string
54-
): Promise<{
55-
fspath: string;
56-
config: unknown;
57-
}> {
50+
): Promise<Configuration> {
5851
const allRules: Record<string, { severity: string }> = [
5952
...getRules(),
6053
...getBetaRules(),
@@ -87,8 +80,8 @@ export class ConfigProvider {
8780
basePath: string,
8881
potentialFileNames: string[],
8982
parser: Function
90-
): Promise<{ fspath: string; config: unknown } | null> {
91-
let foundConfig: { fspath: string; config: unknown };
83+
): Promise<Configuration | null> {
84+
let foundConfig: Configuration;
9285
await Promise.all(
9386
potentialFileNames.map(async (fileName) => {
9487
if (foundConfig) return;
Lines changed: 98 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,116 @@
1-
import { describe, it, expect, jest } from '@jest/globals';
1+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
22

33
import { ConfigProvider } from '../../src/providers/config-provider';
4-
import * as cosmi from 'cosmiconfig';
54

6-
jest.mock('cosmiconfig');
5+
import * as yml from 'yaml';
6+
import * as vsce from 'vscode';
7+
8+
jest.mock('yaml');
9+
jest.mock('vscode');
710

811
describe('Config-Provider', () => {
12+
let provider: ConfigProvider;
13+
const mockFs: any = {};
14+
const mockWorkspace: any = {};
15+
const mockUri = (path: string) => ({ fsPath: path, path });
16+
17+
beforeEach(() => {
18+
provider = new ConfigProvider();
19+
jest.clearAllMocks();
20+
// Mock VSCE
21+
mockFs.stat = jest.fn();
22+
mockFs.readFile = jest.fn();
23+
mockFs.writeFile = jest.fn();
24+
(vsce.workspace.fs as any).stat = mockFs.stat;
25+
(vsce.workspace.fs as any).readFile = mockFs.readFile;
26+
(vsce.workspace.fs as any).writeFile = mockFs.writeFile;
27+
if (vsce.Uri) {
28+
(vsce.Uri as any).file = mockUri;
29+
}
30+
// Mock YAML
31+
jest.spyOn(yml, 'parse').mockImplementation(() => jest.fn());
32+
jest.spyOn(yml, 'Document').mockImplementation((c: any) => c);
33+
// TextEncoder
34+
(global as any).TextEncoder = class {
35+
encode(str: string) {
36+
return Buffer.from(str);
37+
}
38+
};
39+
});
40+
941
it('should be defined', () => {
1042
expect(ConfigProvider).toBeDefined();
1143
});
1244

13-
it('should not error when no config', async () => {
14-
const cosmiMock = {
15-
load: jest.fn(),
16-
search: jest.fn(),
17-
};
18-
const cosmiSpy = jest.spyOn(cosmi, 'cosmiconfig');
19-
cosmiSpy.mockReturnValue(cosmiMock as any);
45+
it('discover: finds JSON config', async () => {
46+
mockFs.stat.mockResolvedValueOnce(true);
47+
mockFs.readFile.mockResolvedValueOnce(Buffer.from('{"foo":1}'));
48+
(yml as any).parse.mockReturnValue({ foo: 1 });
49+
const config = await provider.discover('/path');
50+
expect(config).toBeDefined();
51+
expect(config.config).toEqual({ foo: 1 });
52+
});
2053

21-
const configProvider = new ConfigProvider();
22-
await expect(configProvider.loadConfig()).resolves.toStrictEqual({});
54+
it('discover: finds YAML config', async () => {
55+
mockFs.stat.mockRejectedValueOnce(new Error('not found'));
56+
mockFs.stat.mockRejectedValueOnce(new Error('not found'));
57+
mockFs.stat.mockResolvedValueOnce(true);
58+
mockFs.readFile.mockResolvedValueOnce(Buffer.from('bar: 2'));
59+
(yml as any).parse.mockReturnValue({ bar: 2 });
60+
const config = await provider.discover('/path');
61+
expect(config).toBeDefined();
62+
expect(config.config).toEqual({ bar: 2 });
2363
});
2464

25-
it('should load config when directly passed from settings', async () => {
26-
const cosmiMock = {
27-
load: jest.fn().mockImplementation(() => ({
28-
config: {
29-
rules: {
30-
FlowName: {
31-
severity: 'error',
32-
},
33-
},
34-
exceptions: {},
35-
},
36-
filepath: '',
37-
})),
38-
search: jest.fn(),
39-
};
40-
const cosmiSpy = jest.spyOn(cosmi, 'cosmiconfig');
41-
cosmiSpy.mockReturnValue(cosmiMock as any);
65+
it('discover: creates new config if not found', async () => {
66+
mockFs.stat.mockRejectedValue(new Error('not found'));
67+
mockFs.writeFile.mockResolvedValueOnce(undefined);
68+
const config = await provider.discover('/path');
69+
expect(config).toBeDefined();
70+
expect(config.fspath).toContain('/path/.flow-scanner.yml');
71+
expect(mockFs.writeFile).toHaveBeenCalled();
72+
});
4273

43-
const configProvider = new ConfigProvider();
44-
await expect(
45-
configProvider.loadConfig('some config')
46-
).resolves.toStrictEqual({
47-
rules: {
48-
FlowName: {
49-
severity: 'error',
50-
},
51-
},
52-
exceptions: {},
53-
});
54-
expect(cosmiMock.search).not.toHaveBeenCalled();
74+
it('writeConfigFile: writes a new config file', async () => {
75+
mockFs.writeFile.mockResolvedValueOnce(undefined);
76+
const result = await (provider as any).writeConfigFile(
77+
'flow-scanner',
78+
'/base'
79+
);
80+
expect(result.fspath).toContain('/base/.flow-scanner.yml');
81+
expect(mockFs.writeFile).toHaveBeenCalled();
5582
});
5683

57-
it('should resolve a config via workspace directory', async () => {
58-
const cosmiMock = {
59-
load: jest.fn(),
60-
search: jest.fn().mockImplementation(() => ({
61-
config: {
62-
rules: {
63-
APIVersion: {
64-
severity: 'error',
65-
},
66-
},
67-
exceptions: {},
68-
},
69-
filepath: '',
70-
})),
71-
};
72-
const cosmiSpy = jest.spyOn(cosmi, 'cosmiconfig');
73-
cosmiSpy.mockReturnValue(cosmiMock as any);
84+
it('attemptToReadConfig: returns config if file exists', async () => {
85+
mockFs.stat.mockResolvedValueOnce(true);
86+
mockFs.readFile.mockResolvedValueOnce(Buffer.from('{"a":3}'));
87+
const parser = jest.fn().mockReturnValue({ a: 3 });
88+
const result = await (provider as any).attemptToReadConfig(
89+
'/foo',
90+
['bar.json'],
91+
parser
92+
);
93+
expect(result).toBeDefined();
94+
expect(result.config).toEqual({ a: 3 });
95+
});
96+
97+
it('attemptToReadConfig: returns null if file does not exist', async () => {
98+
mockFs.stat.mockRejectedValue(new Error('not found'));
99+
const parser = jest.fn();
100+
const result = await (provider as any).attemptToReadConfig(
101+
'/foo',
102+
['bar.json'],
103+
parser
104+
);
105+
expect(result).toBeUndefined();
106+
});
74107

75-
const configProvider = new ConfigProvider();
76-
await expect(configProvider.loadConfig()).resolves.toStrictEqual({
77-
rules: {
78-
APIVersion: {
79-
severity: 'error',
80-
},
81-
},
82-
exceptions: {},
83-
});
84-
expect(cosmiMock.load).not.toHaveBeenCalled();
108+
it('loadConfig: returns config from discover', async () => {
109+
const fakeConfig = { rules: { test: { severity: 'error' } } };
110+
jest
111+
.spyOn(provider, 'discover')
112+
.mockResolvedValueOnce({ fspath: '/f', config: fakeConfig });
113+
const result = await provider.loadConfig('/some');
114+
expect(result).toEqual(fakeConfig);
85115
});
86116
});

0 commit comments

Comments
 (0)