@docupace/pipeline 1.0.77 → 1.0.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.prettierrc.cjs +12 -12
  2. package/README.md +135 -135
  3. package/bin/azuread_update_callbacks_for_app.sh +58 -58
  4. package/bin/next-changeset-validate.sh +8 -8
  5. package/bitbucket-pipelines.yml +59 -59
  6. package/eslint.config.js +49 -49
  7. package/jest.config.js +15 -15
  8. package/next/changeset.sh +75 -75
  9. package/next/copy.sh +34 -34
  10. package/next/deploy.sh +40 -40
  11. package/next/execute.sh +123 -123
  12. package/next/sonar.sh +23 -23
  13. package/package.json +1 -1
  14. package/src/auth0/alias.js +79 -79
  15. package/src/auth0/alias.test.js +172 -172
  16. package/src/auth0/cli.js +82 -82
  17. package/src/auth0/configure.js +902 -902
  18. package/src/auth0/configure.test.js +167 -167
  19. package/src/auth0/export.js +48 -48
  20. package/src/auth0/export.test.js +103 -103
  21. package/src/auth0/metadata.js +141 -141
  22. package/src/auth0/metadata.test.js +186 -186
  23. package/src/auth0/vault.js +60 -60
  24. package/src/auth0/vault.test.js +134 -134
  25. package/src/changeset/changelog-wiki.js +328 -328
  26. package/src/changeset/changeset-config.js +34 -34
  27. package/src/changeset/cli.js +237 -237
  28. package/src/changeset/create.js +215 -215
  29. package/src/changeset/package-util.js +87 -87
  30. package/src/changeset/util.js +43 -43
  31. package/src/changeset/validate.js +91 -91
  32. package/src/changeset/version.js +102 -102
  33. package/src/jenkins/setParams.groovy +58 -58
  34. package/src/lib/merge-driver.js +204 -204
  35. package/src/merge/cli-merge-all.js +507 -507
  36. package/src/merge/cli-merge.js +492 -492
  37. package/src/merge/cli-resolve.js +399 -399
  38. package/src/merge/cli.js +38 -38
  39. package/src/merge/merge-changelog.js +87 -87
  40. package/src/merge/merge-package.js +106 -106
  41. package/src/merge/merge-types.js +82 -82
  42. package/src/merge/pr.js +231 -231
  43. package/src/micro/deploy-microservice.js +153 -0
  44. package/src/micro/{ms-publish.js → publish-ms.js} +8 -6
  45. package/src/next/alias.js +53 -53
  46. package/src/next/apache.js +81 -81
  47. package/src/next/cli.js +10 -1
  48. package/src/next/config.js +137 -137
  49. package/src/next/connect.js +226 -226
  50. package/src/next/copy.js +61 -61
  51. package/src/next/deploy.js +666 -633
  52. package/src/next/git-to-semver.js +79 -79
  53. package/src/next/haproxy.js +76 -76
  54. package/src/next/jenkins.js +123 -123
  55. package/src/next/jira.js +84 -84
  56. package/src/next/logger.js +17 -17
  57. package/src/next/pipline.js +114 -114
  58. package/src/next/publish.js +71 -71
  59. package/src/next/pull.js +7 -7
  60. package/src/next/sonar.js +23 -23
  61. package/src/next/test.js +5 -5
  62. package/src/next/util.js +95 -93
  63. package/src/next/vault.js +45 -45
  64. package/terraform/apply.sh +37 -37
  65. package/terraform/init.sh +72 -72
  66. package/terraform/plan.sh +76 -76
  67. package/terraform/validate.sh +17 -17
  68. package/src/micro/cli.js +0 -71
  69. package/src/micro/ms-deploy.js +0 -271
@@ -1,141 +1,141 @@
1
- import fs from 'fs-extra';
2
- import axios from 'axios';
3
- import * as path from 'node:path'; // ESM-compatible import
4
- import { cloneRepository, pushDeployConfig } from '../next/deploy.js';
5
- import { getAuth0AuthToken } from './configure.js';
6
- import { getVaultSecrets } from './vault.js';
7
- import { getVaultSecretsConfigs } from '../next/config.js';
8
-
9
- export async function getAuth0Metadata(site, env, auth0Tenant, jiraTicket) {
10
- try {
11
- const connectDeployRepoPath = await cloneRepository('connect-deploy');
12
-
13
- const envMap = {
14
- dev: 'dev',
15
- preprod: 'qa',
16
- stage: 'stage',
17
- uat: 'uat',
18
- 'prod-www3': 'prod',
19
- 'prod-lax': 'prod',
20
- 'prod-las': 'prod',
21
- };
22
-
23
- process.chdir(`${connectDeployRepoPath}/envs/${env}/${site}`);
24
-
25
- const configPath = path.join('conf', 'application.conf');
26
- const appConf = await fs.readFile(configPath, 'utf-8');
27
-
28
- const connectContext = appConf.match(
29
- /application\.context\s*=\s*"(.*?)"/,
30
- )?.[1];
31
- if (!connectContext) {
32
- console.error('connectContext not found in application.conf. Exiting...');
33
- process.exit(1);
34
- }
35
-
36
- const siteHost = appConf.match(
37
- /docupace\.baseUrlClient\s*=\s*"?https?:\/\/([^/]+)"?/,
38
- )?.[1];
39
- if (!siteHost) {
40
- console.error('siteHost not found in application.conf. Exiting...');
41
- process.exit(1);
42
- }
43
-
44
- const connectEndpoint = `https://${siteHost}${connectContext}`;
45
- console.log('CONNECT_ENDPOINT:', connectEndpoint);
46
-
47
- console.log('Requesting token from Auth0...');
48
- const siteCommonSecretsPath =
49
- getVaultSecretsConfigs(site).siteCommonSecretsPath;
50
- console.log(`siteCommonSecretsPath => ${siteCommonSecretsPath}`);
51
-
52
- const siteCommonSecrets = await getVaultSecrets(siteCommonSecretsPath);
53
-
54
- const auth0Domain =
55
- auth0Tenant === ''
56
- ? `docupace-${envMap[env]}-${site}.us.auth0.com`
57
- : `${auth0Tenant}.us.auth0.com`;
58
-
59
- const auth0ClientId = siteCommonSecrets.AUTH0_CLIENT_ID;
60
- const auth0ClientSecret = siteCommonSecrets.AUTH0_CLIENT_SECRET;
61
-
62
- const auth0Token = await getAuth0AuthToken(
63
- auth0Domain,
64
- auth0ClientId,
65
- auth0ClientSecret,
66
- );
67
-
68
- const auth0Response = await axios.get(
69
- `https://${auth0Domain}/api/v2/clients`,
70
- {
71
- headers: { authorization: `Bearer ${auth0Token}` },
72
- },
73
- );
74
-
75
- const connectClient = auth0Response.data.find(
76
- (client) => client.name === 'docupace-connect',
77
- );
78
-
79
- if (!connectClient) {
80
- console.error('Client "docupace-connect" not found.');
81
- process.exit(1);
82
- }
83
-
84
- const clientId = connectClient.client_id;
85
- console.log('Found client_id:', clientId);
86
-
87
- const metadataRes = await axios.get(
88
- `https://${auth0Domain}/samlp/metadata/${clientId}`,
89
- { responseType: 'text' },
90
- );
91
-
92
- const metadata = metadataRes.data;
93
-
94
- if (metadata.length < 2000) {
95
- console.error(
96
- 'Downloaded metadata looks incorrect (too small). Exiting...',
97
- );
98
- process.exit(1);
99
- }
100
-
101
- await fs.ensureDir('conf/saml');
102
- await fs.writeFile('conf/saml/auth0.xml', metadata);
103
- console.log('SAML metadata written to conf/saml/auth0.xml');
104
-
105
- let newConfig = appConf;
106
-
107
- if (/useSAML\s*=/.test(appConf)) {
108
- newConfig = newConfig.replace(/useSAML\s*=.*/, 'useSAML=true');
109
- } else {
110
- newConfig += '\nuseSAML=true';
111
- }
112
-
113
- const replacements = {
114
- 'saml.clients.auth0.idpMetadataPath': '${samlEnvPath}"/auth0.xml"',
115
- 'saml.clients.auth0.spEntityId': `"${connectEndpoint}"`,
116
- 'saml.clients.auth0.redirectFormInput': '"RelayState"',
117
- 'saml.clients.auth0.idpLoginUrl': `"https://${auth0Domain}/v2/logout?client_id=${clientId}&returnTo=${connectEndpoint}"`,
118
- };
119
-
120
- for (const [key, value] of Object.entries(replacements)) {
121
- const pattern = new RegExp(`${key}\\s*=.*`, 'g');
122
- if (pattern.test(newConfig)) {
123
- newConfig = newConfig.replace(pattern, `${key}=${value}`);
124
- } else {
125
- newConfig += `\n${key}=${value}`;
126
- }
127
- }
128
-
129
- await fs.writeFile(configPath, newConfig);
130
- console.log('The application.conf has been updated.');
131
-
132
- const commitMessage = jiraTicket
133
- ? `${jiraTicket} Auth0 SAML metadata update for site: (${site}) in env: (${env})`
134
- : `Auth0 SAML metadata update for site: (${site}) in env: (${env})`;
135
- console.log('Commit message:', commitMessage);
136
- await pushDeployConfig(connectDeployRepoPath, commitMessage);
137
- } catch (error) {
138
- console.error('Error:', error.message);
139
- process.exit(1);
140
- }
141
- }
1
+ import fs from 'fs-extra';
2
+ import axios from 'axios';
3
+ import * as path from 'node:path'; // ESM-compatible import
4
+ import { cloneRepository, pushDeployConfig } from '../next/deploy.js';
5
+ import { getAuth0AuthToken } from './configure.js';
6
+ import { getVaultSecrets } from './vault.js';
7
+ import { getVaultSecretsConfigs } from '../next/config.js';
8
+
9
+ export async function getAuth0Metadata(site, env, auth0Tenant, jiraTicket) {
10
+ try {
11
+ const connectDeployRepoPath = await cloneRepository('connect-deploy');
12
+
13
+ const envMap = {
14
+ dev: 'dev',
15
+ preprod: 'qa',
16
+ stage: 'stage',
17
+ uat: 'uat',
18
+ 'prod-www3': 'prod',
19
+ 'prod-lax': 'prod',
20
+ 'prod-las': 'prod',
21
+ };
22
+
23
+ process.chdir(`${connectDeployRepoPath}/envs/${env}/${site}`);
24
+
25
+ const configPath = path.join('conf', 'application.conf');
26
+ const appConf = await fs.readFile(configPath, 'utf-8');
27
+
28
+ const connectContext = appConf.match(
29
+ /application\.context\s*=\s*"(.*?)"/,
30
+ )?.[1];
31
+ if (!connectContext) {
32
+ console.error('connectContext not found in application.conf. Exiting...');
33
+ process.exit(1);
34
+ }
35
+
36
+ const siteHost = appConf.match(
37
+ /docupace\.baseUrlClient\s*=\s*"?https?:\/\/([^/]+)"?/,
38
+ )?.[1];
39
+ if (!siteHost) {
40
+ console.error('siteHost not found in application.conf. Exiting...');
41
+ process.exit(1);
42
+ }
43
+
44
+ const connectEndpoint = `https://${siteHost}${connectContext}`;
45
+ console.log('CONNECT_ENDPOINT:', connectEndpoint);
46
+
47
+ console.log('Requesting token from Auth0...');
48
+ const siteCommonSecretsPath =
49
+ getVaultSecretsConfigs(site).siteCommonSecretsPath;
50
+ console.log(`siteCommonSecretsPath => ${siteCommonSecretsPath}`);
51
+
52
+ const siteCommonSecrets = await getVaultSecrets(siteCommonSecretsPath);
53
+
54
+ const auth0Domain =
55
+ auth0Tenant === ''
56
+ ? `docupace-${envMap[env]}-${site}.us.auth0.com`
57
+ : `${auth0Tenant}.us.auth0.com`;
58
+
59
+ const auth0ClientId = siteCommonSecrets.AUTH0_CLIENT_ID;
60
+ const auth0ClientSecret = siteCommonSecrets.AUTH0_CLIENT_SECRET;
61
+
62
+ const auth0Token = await getAuth0AuthToken(
63
+ auth0Domain,
64
+ auth0ClientId,
65
+ auth0ClientSecret,
66
+ );
67
+
68
+ const auth0Response = await axios.get(
69
+ `https://${auth0Domain}/api/v2/clients`,
70
+ {
71
+ headers: { authorization: `Bearer ${auth0Token}` },
72
+ },
73
+ );
74
+
75
+ const connectClient = auth0Response.data.find(
76
+ (client) => client.name === 'docupace-connect',
77
+ );
78
+
79
+ if (!connectClient) {
80
+ console.error('Client "docupace-connect" not found.');
81
+ process.exit(1);
82
+ }
83
+
84
+ const clientId = connectClient.client_id;
85
+ console.log('Found client_id:', clientId);
86
+
87
+ const metadataRes = await axios.get(
88
+ `https://${auth0Domain}/samlp/metadata/${clientId}`,
89
+ { responseType: 'text' },
90
+ );
91
+
92
+ const metadata = metadataRes.data;
93
+
94
+ if (metadata.length < 2000) {
95
+ console.error(
96
+ 'Downloaded metadata looks incorrect (too small). Exiting...',
97
+ );
98
+ process.exit(1);
99
+ }
100
+
101
+ await fs.ensureDir('conf/saml');
102
+ await fs.writeFile('conf/saml/auth0.xml', metadata);
103
+ console.log('SAML metadata written to conf/saml/auth0.xml');
104
+
105
+ let newConfig = appConf;
106
+
107
+ if (/useSAML\s*=/.test(appConf)) {
108
+ newConfig = newConfig.replace(/useSAML\s*=.*/, 'useSAML=true');
109
+ } else {
110
+ newConfig += '\nuseSAML=true';
111
+ }
112
+
113
+ const replacements = {
114
+ 'saml.clients.auth0.idpMetadataPath': '${samlEnvPath}"/auth0.xml"',
115
+ 'saml.clients.auth0.spEntityId': `"${connectEndpoint}"`,
116
+ 'saml.clients.auth0.redirectFormInput': '"RelayState"',
117
+ 'saml.clients.auth0.idpLoginUrl': `"https://${auth0Domain}/v2/logout?client_id=${clientId}&returnTo=${connectEndpoint}"`,
118
+ };
119
+
120
+ for (const [key, value] of Object.entries(replacements)) {
121
+ const pattern = new RegExp(`${key}\\s*=.*`, 'g');
122
+ if (pattern.test(newConfig)) {
123
+ newConfig = newConfig.replace(pattern, `${key}=${value}`);
124
+ } else {
125
+ newConfig += `\n${key}=${value}`;
126
+ }
127
+ }
128
+
129
+ await fs.writeFile(configPath, newConfig);
130
+ console.log('The application.conf has been updated.');
131
+
132
+ const commitMessage = jiraTicket
133
+ ? `${jiraTicket} Auth0 SAML metadata update for site: (${site}) in env: (${env})`
134
+ : `Auth0 SAML metadata update for site: (${site}) in env: (${env})`;
135
+ console.log('Commit message:', commitMessage);
136
+ await pushDeployConfig(connectDeployRepoPath, commitMessage);
137
+ } catch (error) {
138
+ console.error('Error:', error.message);
139
+ process.exit(1);
140
+ }
141
+ }
@@ -1,186 +1,186 @@
1
- import { jest } from '@jest/globals';
2
-
3
- // ----- Mocks (ESM-safe) -----
4
- jest.unstable_mockModule('fs-extra', () => ({
5
- default: {
6
- readFile: jest.fn(),
7
- writeFile: jest.fn(),
8
- ensureDir: jest.fn(),
9
- },
10
- }));
11
-
12
- jest.unstable_mockModule('axios', () => ({
13
- default: {
14
- get: jest.fn(),
15
- },
16
- }));
17
-
18
- jest.unstable_mockModule('path', () => ({
19
- join: jest.fn((...args) => args.join('/')),
20
- }));
21
-
22
- jest.unstable_mockModule('../next/deploy.js', () => ({
23
- cloneRepository: jest.fn(),
24
- pushDeployConfig: jest.fn(),
25
- }));
26
-
27
- jest.unstable_mockModule('./configure.js', () => ({
28
- getAuth0AuthToken: jest.fn(),
29
- }));
30
-
31
- jest.unstable_mockModule('./vault.js', () => ({
32
- getVaultSecrets: jest.fn(),
33
- }));
34
-
35
- jest.unstable_mockModule('../next/config.js', () => ({
36
- getVaultSecretsConfigs: jest.fn(),
37
- }));
38
-
39
- // ----- Imports AFTER mocks -----
40
- const { getAuth0Metadata } = await import('./metadata.js');
41
- const fs = (await import('fs-extra')).default;
42
- const axios = (await import('axios')).default;
43
- const { cloneRepository, pushDeployConfig } = await import('../next/deploy.js');
44
- const { getAuth0AuthToken } = await import('./configure.js');
45
- const { getVaultSecrets } = await import('./vault.js');
46
- const { getVaultSecretsConfigs } = await import('../next/config.js');
47
-
48
- // ----- Shared Setup -----
49
- beforeAll(() => {
50
- jest.spyOn(console, 'log').mockImplementation(() => {});
51
- jest.spyOn(console, 'error').mockImplementation(() => {});
52
- });
53
-
54
- afterAll(() => {
55
- console.log.mockRestore();
56
- console.error.mockRestore();
57
- });
58
-
59
- describe('getAuth0Metadata', () => {
60
- const site = 'example';
61
- const env = 'dev';
62
- const auth0Tenant = '';
63
- const fakeRepoPath = '/tmp/repo';
64
- const fakeAppConf = `
65
- application.context = "/connect"
66
- docupace.baseUrlClient = "https://example.docupace.com"
67
- `;
68
-
69
- const mockSecrets = {
70
- AUTH0_CLIENT_ID: 'client-id',
71
- AUTH0_CLIENT_SECRET: 'client-secret',
72
- };
73
-
74
- const auth0Token = 'fake-token';
75
- const clientId = 'abc123';
76
- const fakeMetadata = '<xml>'.padEnd(2500, 'a') + '</xml>';
77
-
78
- let originalChdir;
79
- let exitSpy;
80
-
81
- beforeEach(() => {
82
- jest.clearAllMocks();
83
- axios.get.mockReset();
84
-
85
- cloneRepository.mockResolvedValue(fakeRepoPath);
86
- getVaultSecretsConfigs.mockReturnValue({
87
- siteCommonSecretsPath: 'vault/path',
88
- });
89
- getVaultSecrets.mockResolvedValue(mockSecrets);
90
- getAuth0AuthToken.mockResolvedValue(auth0Token);
91
- fs.readFile.mockResolvedValue(fakeAppConf);
92
- fs.writeFile.mockResolvedValue();
93
- fs.ensureDir.mockResolvedValue();
94
- pushDeployConfig.mockResolvedValue();
95
-
96
- originalChdir = process.chdir;
97
- process.chdir = jest.fn();
98
-
99
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
100
- });
101
-
102
- afterEach(() => {
103
- process.chdir = originalChdir;
104
- exitSpy.mockRestore();
105
- });
106
-
107
- it('should download and write metadata, update config, and push changes', async () => {
108
- axios.get
109
- .mockResolvedValueOnce({
110
- data: [{ name: 'docupace-connect', client_id: clientId }],
111
- }) // clients
112
- .mockResolvedValueOnce({ data: fakeMetadata }); // metadata
113
-
114
- await getAuth0Metadata(site, env, auth0Tenant);
115
-
116
- expect(cloneRepository).toHaveBeenCalledWith('connect-deploy');
117
- expect(fs.readFile).toHaveBeenCalledWith('conf/application.conf', 'utf-8');
118
- expect(fs.ensureDir).toHaveBeenCalledWith('conf/saml');
119
- expect(fs.writeFile).toHaveBeenCalledWith(
120
- 'conf/saml/auth0.xml',
121
- expect.stringContaining('<xml'),
122
- );
123
- expect(fs.writeFile).toHaveBeenCalledWith(
124
- 'conf/application.conf',
125
- expect.stringContaining('useSAML=true'),
126
- );
127
- expect(pushDeployConfig).toHaveBeenCalled();
128
- });
129
-
130
- it('should exit if docupace-connect client is not found', async () => {
131
- axios.get.mockResolvedValueOnce({ data: [] }); // empty clients
132
-
133
- await getAuth0Metadata(site, env, auth0Tenant);
134
-
135
- expect(process.exit).toHaveBeenCalledWith(1);
136
- expect(console.error).toHaveBeenCalledWith(
137
- 'Client "docupace-connect" not found.',
138
- );
139
- });
140
-
141
- it('should exit if metadata is too small', async () => {
142
- axios.get
143
- .mockResolvedValueOnce({
144
- data: [{ name: 'docupace-connect', client_id: clientId }],
145
- }) // clients
146
- .mockResolvedValueOnce({ data: '<small>' }); // small metadata
147
-
148
- await getAuth0Metadata(site, env, auth0Tenant);
149
-
150
- expect(process.exit).toHaveBeenCalledWith(1);
151
- expect(console.error).toHaveBeenCalledWith(
152
- 'Downloaded metadata looks incorrect (too small). Exiting...',
153
- );
154
- });
155
-
156
- it('should exit if connectContext is missing', async () => {
157
- fs.readFile.mockResolvedValue(`docupace.baseUrlClient = "https://abc.com"`); // no context
158
-
159
- await getAuth0Metadata(site, env, auth0Tenant);
160
-
161
- expect(process.exit).toHaveBeenCalledWith(1);
162
- expect(console.error).toHaveBeenCalledWith(
163
- 'connectContext not found in application.conf. Exiting...',
164
- );
165
- });
166
-
167
- it('should exit if siteHost is missing', async () => {
168
- fs.readFile.mockResolvedValue(`application.context = "/abc"`); // no host
169
-
170
- await getAuth0Metadata(site, env, auth0Tenant);
171
-
172
- expect(process.exit).toHaveBeenCalledWith(1);
173
- expect(console.error).toHaveBeenCalledWith(
174
- 'siteHost not found in application.conf. Exiting...',
175
- );
176
- });
177
-
178
- it('should exit on unknown error', async () => {
179
- getVaultSecrets.mockRejectedValue(new Error('boom'));
180
-
181
- await getAuth0Metadata(site, env, auth0Tenant);
182
-
183
- expect(process.exit).toHaveBeenCalledWith(1);
184
- expect(console.error).toHaveBeenCalledWith('Error:', 'boom');
185
- });
186
- });
1
+ import { jest } from '@jest/globals';
2
+
3
+ // ----- Mocks (ESM-safe) -----
4
+ jest.unstable_mockModule('fs-extra', () => ({
5
+ default: {
6
+ readFile: jest.fn(),
7
+ writeFile: jest.fn(),
8
+ ensureDir: jest.fn(),
9
+ },
10
+ }));
11
+
12
+ jest.unstable_mockModule('axios', () => ({
13
+ default: {
14
+ get: jest.fn(),
15
+ },
16
+ }));
17
+
18
+ jest.unstable_mockModule('path', () => ({
19
+ join: jest.fn((...args) => args.join('/')),
20
+ }));
21
+
22
+ jest.unstable_mockModule('../next/deploy.js', () => ({
23
+ cloneRepository: jest.fn(),
24
+ pushDeployConfig: jest.fn(),
25
+ }));
26
+
27
+ jest.unstable_mockModule('./configure.js', () => ({
28
+ getAuth0AuthToken: jest.fn(),
29
+ }));
30
+
31
+ jest.unstable_mockModule('./vault.js', () => ({
32
+ getVaultSecrets: jest.fn(),
33
+ }));
34
+
35
+ jest.unstable_mockModule('../next/config.js', () => ({
36
+ getVaultSecretsConfigs: jest.fn(),
37
+ }));
38
+
39
+ // ----- Imports AFTER mocks -----
40
+ const { getAuth0Metadata } = await import('./metadata.js');
41
+ const fs = (await import('fs-extra')).default;
42
+ const axios = (await import('axios')).default;
43
+ const { cloneRepository, pushDeployConfig } = await import('../next/deploy.js');
44
+ const { getAuth0AuthToken } = await import('./configure.js');
45
+ const { getVaultSecrets } = await import('./vault.js');
46
+ const { getVaultSecretsConfigs } = await import('../next/config.js');
47
+
48
+ // ----- Shared Setup -----
49
+ beforeAll(() => {
50
+ jest.spyOn(console, 'log').mockImplementation(() => {});
51
+ jest.spyOn(console, 'error').mockImplementation(() => {});
52
+ });
53
+
54
+ afterAll(() => {
55
+ console.log.mockRestore();
56
+ console.error.mockRestore();
57
+ });
58
+
59
+ describe('getAuth0Metadata', () => {
60
+ const site = 'example';
61
+ const env = 'dev';
62
+ const auth0Tenant = '';
63
+ const fakeRepoPath = '/tmp/repo';
64
+ const fakeAppConf = `
65
+ application.context = "/connect"
66
+ docupace.baseUrlClient = "https://example.docupace.com"
67
+ `;
68
+
69
+ const mockSecrets = {
70
+ AUTH0_CLIENT_ID: 'client-id',
71
+ AUTH0_CLIENT_SECRET: 'client-secret',
72
+ };
73
+
74
+ const auth0Token = 'fake-token';
75
+ const clientId = 'abc123';
76
+ const fakeMetadata = '<xml>'.padEnd(2500, 'a') + '</xml>';
77
+
78
+ let originalChdir;
79
+ let exitSpy;
80
+
81
+ beforeEach(() => {
82
+ jest.clearAllMocks();
83
+ axios.get.mockReset();
84
+
85
+ cloneRepository.mockResolvedValue(fakeRepoPath);
86
+ getVaultSecretsConfigs.mockReturnValue({
87
+ siteCommonSecretsPath: 'vault/path',
88
+ });
89
+ getVaultSecrets.mockResolvedValue(mockSecrets);
90
+ getAuth0AuthToken.mockResolvedValue(auth0Token);
91
+ fs.readFile.mockResolvedValue(fakeAppConf);
92
+ fs.writeFile.mockResolvedValue();
93
+ fs.ensureDir.mockResolvedValue();
94
+ pushDeployConfig.mockResolvedValue();
95
+
96
+ originalChdir = process.chdir;
97
+ process.chdir = jest.fn();
98
+
99
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
100
+ });
101
+
102
+ afterEach(() => {
103
+ process.chdir = originalChdir;
104
+ exitSpy.mockRestore();
105
+ });
106
+
107
+ it('should download and write metadata, update config, and push changes', async () => {
108
+ axios.get
109
+ .mockResolvedValueOnce({
110
+ data: [{ name: 'docupace-connect', client_id: clientId }],
111
+ }) // clients
112
+ .mockResolvedValueOnce({ data: fakeMetadata }); // metadata
113
+
114
+ await getAuth0Metadata(site, env, auth0Tenant);
115
+
116
+ expect(cloneRepository).toHaveBeenCalledWith('connect-deploy');
117
+ expect(fs.readFile).toHaveBeenCalledWith('conf/application.conf', 'utf-8');
118
+ expect(fs.ensureDir).toHaveBeenCalledWith('conf/saml');
119
+ expect(fs.writeFile).toHaveBeenCalledWith(
120
+ 'conf/saml/auth0.xml',
121
+ expect.stringContaining('<xml'),
122
+ );
123
+ expect(fs.writeFile).toHaveBeenCalledWith(
124
+ 'conf/application.conf',
125
+ expect.stringContaining('useSAML=true'),
126
+ );
127
+ expect(pushDeployConfig).toHaveBeenCalled();
128
+ });
129
+
130
+ it('should exit if docupace-connect client is not found', async () => {
131
+ axios.get.mockResolvedValueOnce({ data: [] }); // empty clients
132
+
133
+ await getAuth0Metadata(site, env, auth0Tenant);
134
+
135
+ expect(process.exit).toHaveBeenCalledWith(1);
136
+ expect(console.error).toHaveBeenCalledWith(
137
+ 'Client "docupace-connect" not found.',
138
+ );
139
+ });
140
+
141
+ it('should exit if metadata is too small', async () => {
142
+ axios.get
143
+ .mockResolvedValueOnce({
144
+ data: [{ name: 'docupace-connect', client_id: clientId }],
145
+ }) // clients
146
+ .mockResolvedValueOnce({ data: '<small>' }); // small metadata
147
+
148
+ await getAuth0Metadata(site, env, auth0Tenant);
149
+
150
+ expect(process.exit).toHaveBeenCalledWith(1);
151
+ expect(console.error).toHaveBeenCalledWith(
152
+ 'Downloaded metadata looks incorrect (too small). Exiting...',
153
+ );
154
+ });
155
+
156
+ it('should exit if connectContext is missing', async () => {
157
+ fs.readFile.mockResolvedValue(`docupace.baseUrlClient = "https://abc.com"`); // no context
158
+
159
+ await getAuth0Metadata(site, env, auth0Tenant);
160
+
161
+ expect(process.exit).toHaveBeenCalledWith(1);
162
+ expect(console.error).toHaveBeenCalledWith(
163
+ 'connectContext not found in application.conf. Exiting...',
164
+ );
165
+ });
166
+
167
+ it('should exit if siteHost is missing', async () => {
168
+ fs.readFile.mockResolvedValue(`application.context = "/abc"`); // no host
169
+
170
+ await getAuth0Metadata(site, env, auth0Tenant);
171
+
172
+ expect(process.exit).toHaveBeenCalledWith(1);
173
+ expect(console.error).toHaveBeenCalledWith(
174
+ 'siteHost not found in application.conf. Exiting...',
175
+ );
176
+ });
177
+
178
+ it('should exit on unknown error', async () => {
179
+ getVaultSecrets.mockRejectedValue(new Error('boom'));
180
+
181
+ await getAuth0Metadata(site, env, auth0Tenant);
182
+
183
+ expect(process.exit).toHaveBeenCalledWith(1);
184
+ expect(console.error).toHaveBeenCalledWith('Error:', 'boom');
185
+ });
186
+ });