Skip to content

Commit 55a1025

Browse files
jimthompson5802rocketstack-mattjpgough-ms
authored
feat(cli): extend hub commands with patterns, standards, domains, controls, decorators, and auth plugin support (finos#2472)
* feat: add support for patterns and standards in CalmHubClient * fix(cli): update hub command interfaces to use calmHubOptions * fix(cli): update pushPatternVersion to send raw pattern JSON and adjust parameters * fix(cli): validate standard file as JSON before pushing to hub * feat: add domain and control management commands to CalmHub CLI * fix(cli): update createDomain to return name and location, adjust tests accordingly * feat(cli): add control configuration commands to CalmHub CLI * fix(cli): standardize command names for control configurations * refactor(hub-commands): rename control commands to control-requirement for clarity * refactor(hub-commands): rename control-config to control-configuration for consistency * fix(calm-hub-client): correct indentation for API endpoint URL in get request * docs(hub-client): enhance JSDoc comments for clarity and completeness * refactor(hub-commands): consolidate control configuration commands and update related tests * refactor(hub-commands): enhance documentation for runListControlConfigurations function * refactor(hub-commands): change configId type from string to number and update related tests * feat(hub-commands): add name and description options to pushControlRequirement command * feat(hub-commands): add description field to control requirements output * feat(hub-commands): name and description optional for pushControlRequirement and add fallback logic * docs(hub): update for Standards, Patterns, Domains and Controls * fix(docs): correct typo in manual verification instructions * feat(hub-commands): consolidate list control requirements commands * refactor(cli): remove deprecated list control-requirement-versions command test * refactor(cli): remove test for deprecated control-configuration-versions command * refactor(hub-commands): rename 'id' to 'configId' in control configuration summaries * refactor(docs): update output format and remove deprecated list control requirement versions * refactor(cli): update pushPatternVersion to include pattern name and description in request body * docs(hub): update warning message and enhance description of hub command group * docs: update CALM Hub CLI documentation with new commands and usage details --------- Co-authored-by: Matthew Bain <66839492+rocketstack-matt@users.noreply.github.com> Co-authored-by: jpgough-ms <152306432+jpgough-ms@users.noreply.github.com>
1 parent e72bf6f commit 55a1025

11 files changed

Lines changed: 4919 additions & 10 deletions

File tree

cli/DEVELOPER_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ $ npm pack --pack-destination /tmp
6464
# Note: this will replace an existing calm cli install in the global area
6565
$ npm install -g /tmp/finos-calm-cli-<version-identifier>.tgz
6666

67-
# run manual verfication of the new version, e.g,.
67+
# run manual verification of the new version, e.g,.
6868
# calm --help
6969
# calm init-ai --help
7070
```

cli/src/cli.spec.ts

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,338 @@ describe('CLI Commands', () => {
780780
});
781781
});
782782

783+
describe('create domain command', () => {
784+
beforeEach(async () => {
785+
hubCommandsModule = await import('./command-helpers/hub-commands');
786+
vi.spyOn(hubCommandsModule, 'runCreateDomain').mockResolvedValue(undefined);
787+
});
788+
789+
it('calls runCreateDomain with name and hub url', async () => {
790+
await program.parseAsync([
791+
'node', 'cli.js', 'hub', 'create', 'domain',
792+
'--name', 'risk',
793+
'--calm-hub-url', 'http://hub',
794+
]);
795+
796+
expect(hubCommandsModule.runCreateDomain).toHaveBeenCalledWith(expect.objectContaining({
797+
name: 'risk',
798+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
799+
}));
800+
});
801+
802+
it('passes --format pretty through to the handler', async () => {
803+
await program.parseAsync([
804+
'node', 'cli.js', 'hub', 'create', 'domain',
805+
'--name', 'risk',
806+
'--format', 'pretty',
807+
]);
808+
809+
expect(hubCommandsModule.runCreateDomain).toHaveBeenCalledWith(expect.objectContaining({
810+
format: 'pretty',
811+
}));
812+
});
813+
});
814+
815+
describe('create control-requirement command', () => {
816+
beforeEach(async () => {
817+
hubCommandsModule = await import('./command-helpers/hub-commands');
818+
vi.spyOn(hubCommandsModule, 'runCreateControlRequirement').mockResolvedValue(undefined);
819+
});
820+
821+
it('calls runCreateControlRequirement with domain, name, description and file', async () => {
822+
await program.parseAsync([
823+
'node', 'cli.js', 'hub', 'create', 'control-requirement', 'req.json',
824+
'--domain', 'risk',
825+
'--name', 'my-control',
826+
'--description', 'A control',
827+
'--calm-hub-url', 'http://hub',
828+
]);
829+
830+
expect(hubCommandsModule.runCreateControlRequirement).toHaveBeenCalledWith(expect.objectContaining({
831+
domain: 'risk',
832+
name: 'my-control',
833+
description: 'A control',
834+
file: 'req.json',
835+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
836+
}));
837+
});
838+
});
839+
840+
describe('list domains command', () => {
841+
beforeEach(async () => {
842+
hubCommandsModule = await import('./command-helpers/hub-commands');
843+
vi.spyOn(hubCommandsModule, 'runListDomains').mockResolvedValue(undefined);
844+
});
845+
846+
it('calls runListDomains with hub url', async () => {
847+
await program.parseAsync([
848+
'node', 'cli.js', 'hub', 'list', 'domains',
849+
'--calm-hub-url', 'http://hub',
850+
]);
851+
852+
expect(hubCommandsModule.runListDomains).toHaveBeenCalledWith(expect.objectContaining({
853+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
854+
}));
855+
});
856+
857+
it('passes --format pretty through to the handler', async () => {
858+
await program.parseAsync([
859+
'node', 'cli.js', 'hub', 'list', 'domains',
860+
'--format', 'pretty',
861+
]);
862+
863+
expect(hubCommandsModule.runListDomains).toHaveBeenCalledWith(expect.objectContaining({
864+
format: 'pretty',
865+
}));
866+
});
867+
});
868+
869+
describe('list control-requirements command', () => {
870+
beforeEach(async () => {
871+
hubCommandsModule = await import('./command-helpers/hub-commands');
872+
vi.spyOn(hubCommandsModule, 'runListControlRequirements').mockResolvedValue(undefined);
873+
});
874+
875+
it('calls runListControlRequirements with domain and hub url', async () => {
876+
await program.parseAsync([
877+
'node', 'cli.js', 'hub', 'list', 'control-requirements',
878+
'--domain', 'risk',
879+
'--calm-hub-url', 'http://hub',
880+
]);
881+
882+
expect(hubCommandsModule.runListControlRequirements).toHaveBeenCalledWith(expect.objectContaining({
883+
domain: 'risk',
884+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
885+
}));
886+
});
887+
888+
it('passes --format pretty through to the handler', async () => {
889+
await program.parseAsync([
890+
'node', 'cli.js', 'hub', 'list', 'control-requirements',
891+
'--domain', 'risk',
892+
'--format', 'pretty',
893+
]);
894+
895+
expect(hubCommandsModule.runListControlRequirements).toHaveBeenCalledWith(expect.objectContaining({
896+
format: 'pretty',
897+
}));
898+
});
899+
});
900+
901+
describe('push control-requirement command', () => {
902+
beforeEach(async () => {
903+
hubCommandsModule = await import('./command-helpers/hub-commands');
904+
vi.spyOn(hubCommandsModule, 'runPushControlRequirement').mockResolvedValue(undefined);
905+
});
906+
907+
it('calls runPushControlRequirement with domain, controlId, version, metadata and file', async () => {
908+
await program.parseAsync([
909+
'node', 'cli.js', 'hub', 'push', 'control-requirement', 'req.json',
910+
'--domain', 'risk',
911+
'--control-id', '1',
912+
'--ver', '1.0.0',
913+
'--name', 'req-name',
914+
'--description', 'req-description',
915+
'--calm-hub-url', 'http://hub',
916+
]);
917+
918+
expect(hubCommandsModule.runPushControlRequirement).toHaveBeenCalledWith(expect.objectContaining({
919+
domain: 'risk',
920+
controlId: '1',
921+
version: '1.0.0',
922+
name: 'req-name',
923+
description: 'req-description',
924+
file: 'req.json',
925+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
926+
}));
927+
});
928+
929+
it('calls runPushControlRequirement when name and description are omitted', async () => {
930+
await program.parseAsync([
931+
'node', 'cli.js', 'hub', 'push', 'control-requirement', 'req.json',
932+
'--domain', 'risk',
933+
'--control-id', '1',
934+
'--ver', '1.0.0',
935+
'--calm-hub-url', 'http://hub',
936+
]);
937+
938+
expect(hubCommandsModule.runPushControlRequirement).toHaveBeenCalledWith(expect.objectContaining({
939+
domain: 'risk',
940+
controlId: '1',
941+
version: '1.0.0',
942+
name: undefined,
943+
description: undefined,
944+
file: 'req.json',
945+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
946+
}));
947+
});
948+
});
949+
950+
describe('pull control-requirement command', () => {
951+
beforeEach(async () => {
952+
hubCommandsModule = await import('./command-helpers/hub-commands');
953+
vi.spyOn(hubCommandsModule, 'runPullControlRequirement').mockResolvedValue(undefined);
954+
});
955+
956+
it('calls runPullControlRequirement with domain, controlId and version', async () => {
957+
await program.parseAsync([
958+
'node', 'cli.js', 'hub', 'pull', 'control-requirement',
959+
'--domain', 'risk',
960+
'--control-id', '1',
961+
'--ver', '1.0.0',
962+
'--calm-hub-url', 'http://hub',
963+
]);
964+
965+
expect(hubCommandsModule.runPullControlRequirement).toHaveBeenCalledWith(expect.objectContaining({
966+
domain: 'risk',
967+
controlId: '1',
968+
version: '1.0.0',
969+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
970+
}));
971+
});
972+
973+
it('passes --output through to the handler', async () => {
974+
await program.parseAsync([
975+
'node', 'cli.js', 'hub', 'pull', 'control-requirement',
976+
'--domain', 'risk',
977+
'--control-id', '1',
978+
'--ver', '1.0.0',
979+
'--output', 'out.json',
980+
]);
981+
982+
expect(hubCommandsModule.runPullControlRequirement).toHaveBeenCalledWith(expect.objectContaining({
983+
output: 'out.json',
984+
}));
985+
});
986+
});
987+
988+
describe('push control-configuration command', () => {
989+
beforeEach(async () => {
990+
hubCommandsModule = await import('./command-helpers/hub-commands');
991+
vi.spyOn(hubCommandsModule, 'runPushControlConfiguration').mockResolvedValue(undefined);
992+
});
993+
994+
it('calls runPushControlConfiguration with domain, controlId, configId, version and file', async () => {
995+
await program.parseAsync([
996+
'node', 'cli.js', 'hub', 'push', 'control-configuration', 'cfg.json',
997+
'--domain', 'risk',
998+
'--control-id', '1',
999+
'--config-id', 'cfg-1',
1000+
'--ver', '1.0.0',
1001+
'--calm-hub-url', 'http://hub',
1002+
]);
1003+
1004+
expect(hubCommandsModule.runPushControlConfiguration).toHaveBeenCalledWith(expect.objectContaining({
1005+
domain: 'risk',
1006+
controlId: '1',
1007+
configId: 'cfg-1',
1008+
version: '1.0.0',
1009+
file: 'cfg.json',
1010+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
1011+
}));
1012+
});
1013+
});
1014+
1015+
describe('pull control-configuration command', () => {
1016+
beforeEach(async () => {
1017+
hubCommandsModule = await import('./command-helpers/hub-commands');
1018+
vi.spyOn(hubCommandsModule, 'runPullControlConfiguration').mockResolvedValue(undefined);
1019+
});
1020+
1021+
it('calls runPullControlConfiguration with domain, controlId, configId and version', async () => {
1022+
await program.parseAsync([
1023+
'node', 'cli.js', 'hub', 'pull', 'control-configuration',
1024+
'--domain', 'risk',
1025+
'--control-id', '1',
1026+
'--config-id', 'cfg-1',
1027+
'--ver', '1.0.0',
1028+
'--calm-hub-url', 'http://hub',
1029+
]);
1030+
1031+
expect(hubCommandsModule.runPullControlConfiguration).toHaveBeenCalledWith(expect.objectContaining({
1032+
domain: 'risk',
1033+
controlId: '1',
1034+
configId: 'cfg-1',
1035+
version: '1.0.0',
1036+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
1037+
}));
1038+
});
1039+
1040+
it('passes --output through to the handler', async () => {
1041+
await program.parseAsync([
1042+
'node', 'cli.js', 'hub', 'pull', 'control-configuration',
1043+
'--domain', 'risk',
1044+
'--control-id', '1',
1045+
'--config-id', 'cfg-1',
1046+
'--ver', '1.0.0',
1047+
'--output', 'out.json',
1048+
]);
1049+
1050+
expect(hubCommandsModule.runPullControlConfiguration).toHaveBeenCalledWith(expect.objectContaining({
1051+
output: 'out.json',
1052+
}));
1053+
});
1054+
});
1055+
1056+
describe('create control-configuration command', () => {
1057+
beforeEach(async () => {
1058+
hubCommandsModule = await import('./command-helpers/hub-commands');
1059+
vi.spyOn(hubCommandsModule, 'runCreateControlConfiguration').mockResolvedValue(undefined);
1060+
});
1061+
1062+
it('calls runCreateControlConfiguration with domain, controlId and file', async () => {
1063+
await program.parseAsync([
1064+
'node', 'cli.js', 'hub', 'create', 'control-configuration', 'cfg.json',
1065+
'--domain', 'risk',
1066+
'--control-id', '1',
1067+
'--calm-hub-url', 'http://hub',
1068+
]);
1069+
1070+
expect(hubCommandsModule.runCreateControlConfiguration).toHaveBeenCalledWith(expect.objectContaining({
1071+
domain: 'risk',
1072+
controlId: '1',
1073+
file: 'cfg.json',
1074+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
1075+
}));
1076+
});
1077+
});
1078+
1079+
describe('list control-configurations command', () => {
1080+
beforeEach(async () => {
1081+
hubCommandsModule = await import('./command-helpers/hub-commands');
1082+
vi.spyOn(hubCommandsModule, 'runListControlConfigurations').mockResolvedValue(undefined);
1083+
});
1084+
1085+
it('calls runListControlConfigurations with domain and controlId', async () => {
1086+
await program.parseAsync([
1087+
'node', 'cli.js', 'hub', 'list', 'control-configurations',
1088+
'--domain', 'risk',
1089+
'--control-id', '1',
1090+
'--calm-hub-url', 'http://hub',
1091+
]);
1092+
1093+
expect(hubCommandsModule.runListControlConfigurations).toHaveBeenCalledWith(expect.objectContaining({
1094+
domain: 'risk',
1095+
controlId: '1',
1096+
calmHubOptions: expect.objectContaining({ calmHubUrl: 'http://hub' }),
1097+
}));
1098+
});
1099+
1100+
it('passes --format pretty through to the handler', async () => {
1101+
await program.parseAsync([
1102+
'node', 'cli.js', 'hub', 'list', 'control-configurations',
1103+
'--domain', 'risk',
1104+
'--control-id', '1',
1105+
'--format', 'pretty',
1106+
]);
1107+
1108+
expect(hubCommandsModule.runListControlConfigurations).toHaveBeenCalledWith(expect.objectContaining({
1109+
format: 'pretty',
1110+
}));
1111+
});
1112+
1113+
});
1114+
7831115
});
7841116

7851117
describe('parseDocumentLoaderConfig', () => {

0 commit comments

Comments
 (0)