Skip to content

Commit a44eb28

Browse files
authored
[PM-26672] Add Google Workspace integration tests to CI pipeline (#909)
- reorganize integration test files to allow for future additions - add Google Workspace integration tests to the Github workflow - refactor to run tests selective based on changed files and use Azure Key Vault
1 parent ab43655 commit a44eb28

15 files changed

Lines changed: 167 additions & 83 deletions

.github/workflows/integration-test.yml

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,36 @@ name: Integration Testing
22

33
on:
44
workflow_dispatch:
5+
# Integration tests are slow, so only run them if relevant files have changed.
6+
# This is done at the workflow level and at the job level.
7+
# Make sure these triggers stay consistent with the 'changed-files' job.
58
push:
69
branches:
7-
- "main"
10+
- 'main'
11+
- 'rc'
812
paths:
913
- ".github/workflows/integration-test.yml" # this file
10-
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
11-
- "./utils/**/*" # any change to test fixtures
12-
- "./docker-compose.yml" # any change to Docker configuration
13-
- "./package.json" # dependencies
14+
- "docker-compose.yml" # any change to Docker configuration
15+
- "package.json" # dependencies
16+
- "utils/**" # any change to test fixtures
17+
- "src/services/sync.service.ts" # core sync service used by all directory services
18+
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
19+
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
20+
# Add directory services here as we add test coverage
1421
pull_request:
1522
paths:
1623
- ".github/workflows/integration-test.yml" # this file
17-
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
18-
- "./utils/**/*" # any change to test fixtures
19-
- "./docker-compose.yml" # any change to Docker configuration
20-
- "./package.json" # dependencies
24+
- "docker-compose.yml" # any change to Docker configuration
25+
- "package.json" # dependencies
26+
- "utils/**" # any change to test fixtures
27+
- "src/services/sync.service.ts" # core sync service used by all directory services
28+
- "src/services/directory-services/ldap-directory.service*" # LDAP directory service
29+
- "src/services/directory-services/gsuite-directory.service*" # Google Workspace directory service
30+
# Add directory services here as we add test coverage
2131
permissions:
2232
contents: read
2333
checks: write # required by dorny/test-reporter to upload its results
34+
id-token: write # required to use OIDC to login to Azure Key Vault
2435
jobs:
2536
testing:
2637
name: Run tests
@@ -50,23 +61,79 @@ jobs:
5061
- name: Install Node dependencies
5162
run: npm ci
5263

53-
- name: Install mkcert
64+
# Get secrets from Azure Key Vault
65+
- name: Azure Login
66+
uses: bitwarden/gh-actions/azure-login@main
67+
with:
68+
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
69+
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
70+
client_id: ${{ secrets.AZURE_CLIENT_ID }}
71+
72+
- name: Get KV Secrets
73+
id: get-kv-secrets
74+
uses: bitwarden/gh-actions/get-keyvault-secrets@main
75+
with:
76+
keyvault: gh-directory-connector
77+
secrets: "GOOGLE-ADMIN-USER,GOOGLE-CLIENT-EMAIL,GOOGLE-DOMAIN,GOOGLE-PRIVATE-KEY"
78+
79+
- name: Azure Logout
80+
uses: bitwarden/gh-actions/azure-logout@main
81+
82+
# Only run relevant tests depending on what files have changed.
83+
# This should be kept consistent with the workflow level triggers.
84+
# Note: docker-compose.yml is only used for ldap for now
85+
- name: Get changed files
86+
id: changed-files
87+
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
88+
with:
89+
list-files: shell
90+
token: ${{ secrets.GITHUB_TOKEN }}
91+
# Add directory services here as we add test coverage
92+
filters: |
93+
common:
94+
- '.github/workflows/integration-test.yml'
95+
- 'utils/**'
96+
- 'package.json'
97+
- 'src/services/sync.service.ts'
98+
ldap:
99+
- 'docker-compose.yml'
100+
- 'src/services/directory-services/ldap-directory.service*'
101+
google:
102+
- 'src/services/directory-services/gsuite-directory.service*'
103+
104+
# LDAP
105+
- name: Setup LDAP integration tests
106+
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
54107
run: |
55108
sudo apt-get update
56109
sudo apt-get -y install mkcert
57-
58-
- name: Setup LDAP integration tests
59-
run: npm run test:integration:setup
110+
npm run test:integration:setup
60111
61112
- name: Run LDAP integration tests
62-
run: npx jest ldap-directory.service.integration.spec.ts --coverage
113+
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.ldap == 'true'
114+
env:
115+
JEST_JUNIT_UNIQUE_OUTPUT_NAME: "true" # avoids junit outputs from clashing
116+
run: npx jest ldap-directory.service.integration.spec.ts --coverage --coverageDirectory=coverage-ldap
117+
118+
# Google Workspace
119+
- name: Run Google Workspace integration tests
120+
if: steps.changed-files.outputs.common == 'true' || steps.changed-files.outputs.google == 'true'
121+
env:
122+
GOOGLE_DOMAIN: ${{ steps.get-kv-secrets.outputs.GOOGLE-DOMAIN }}
123+
GOOGLE_ADMIN_USER: ${{ steps.get-kv-secrets.outputs.GOOGLE-ADMIN-USER }}
124+
GOOGLE_CLIENT_EMAIL: ${{ steps.get-kv-secrets.outputs.GOOGLE-CLIENT-EMAIL }}
125+
GOOGLE_PRIVATE_KEY: ${{ steps.get-kv-secrets.outputs.GOOGLE-PRIVATE-KEY }}
126+
JEST_JUNIT_UNIQUE_OUTPUT_NAME: "true" # avoids junit outputs from clashing
127+
run: |
128+
npx jest gsuite-directory.service.integration.spec.ts --coverage --coverageDirectory=coverage-google
63129
64130
- name: Report test results
131+
id: report
65132
uses: dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3 # v2.1.1
66-
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
133+
if: github.event.pull_request.head.repo.full_name == github.repository && !cancelled()
67134
with:
68135
name: Test Results
69-
path: "junit.xml"
136+
path: "junit.xml*"
70137
reporter: jest-junit
71138
fail-on-error: true
72139

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ build-cli
3333
.angular/cache
3434

3535
# Testing
36-
coverage
37-
junit.xml
36+
coverage*
37+
junit.xml*
3838

3939
# Misc
4040
*.crx

src/abstractions/directory-factory.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DirectoryType } from "@/src/enums/directoryType";
2-
import { IDirectoryService } from "@/src/services/directory.service";
2+
import { IDirectoryService } from "@/src/services/directory-services/directory.service";
33

44
export abstract class DirectoryFactoryService {
55
abstract createService(type: DirectoryType): IDirectoryService;

src/locales/en/messages.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,5 +768,8 @@
768768
},
769769
"launchWebVault": {
770770
"message": "Launch Web Vault"
771+
},
772+
"authenticationFailed": {
773+
"message": "Authentication failed"
771774
}
772775
}

src/services/directory-factory.service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { DirectoryFactoryService } from "../abstractions/directory-factory.servi
55
import { StateService } from "../abstractions/state.service";
66
import { DirectoryType } from "../enums/directoryType";
77

8-
import { EntraIdDirectoryService } from "./entra-id-directory.service";
9-
import { GSuiteDirectoryService } from "./gsuite-directory.service";
10-
import { LdapDirectoryService } from "./ldap-directory.service";
11-
import { OktaDirectoryService } from "./okta-directory.service";
12-
import { OneLoginDirectoryService } from "./onelogin-directory.service";
8+
import { EntraIdDirectoryService } from "./directory-services/entra-id-directory.service";
9+
import { GSuiteDirectoryService } from "./directory-services/gsuite-directory.service";
10+
import { LdapDirectoryService } from "./directory-services/ldap-directory.service";
11+
import { OktaDirectoryService } from "./directory-services/okta-directory.service";
12+
import { OneLoginDirectoryService } from "./directory-services/onelogin-directory.service";
1313

1414
export class DefaultDirectoryFactoryService implements DirectoryFactoryService {
1515
constructor(

src/services/directory.service.ts renamed to src/services/directory-services/directory.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { GroupEntry } from "../models/groupEntry";
2-
import { UserEntry } from "../models/userEntry";
1+
import { GroupEntry } from "../../models/groupEntry";
2+
import { UserEntry } from "../../models/userEntry";
33

44
export interface IDirectoryService {
55
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;

src/services/entra-id-directory.service.ts renamed to src/services/directory-services/entra-id-directory.service.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import * as graphType from "@microsoft/microsoft-graph-types";
77
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
88
import { LogService } from "@/jslib/common/src/abstractions/log.service";
99

10-
import { StateService } from "../abstractions/state.service";
11-
import { DirectoryType } from "../enums/directoryType";
12-
import { EntraIdConfiguration } from "../models/entraIdConfiguration";
13-
import { GroupEntry } from "../models/groupEntry";
14-
import { SyncConfiguration } from "../models/syncConfiguration";
15-
import { UserEntry } from "../models/userEntry";
16-
17-
import { BaseDirectoryService } from "./baseDirectory.service";
10+
import { StateService } from "../../abstractions/state.service";
11+
import { DirectoryType } from "../../enums/directoryType";
12+
import { EntraIdConfiguration } from "../../models/entraIdConfiguration";
13+
import { GroupEntry } from "../../models/groupEntry";
14+
import { SyncConfiguration } from "../../models/syncConfiguration";
15+
import { UserEntry } from "../../models/userEntry";
16+
import { BaseDirectoryService } from "../baseDirectory.service";
17+
1818
import { IDirectoryService } from "./directory.service";
1919

2020
const EntraIdPublicIdentityAuthority = "login.microsoftonline.com";

src/services/gsuite-directory.service.integration.spec.ts renamed to src/services/directory-services/gsuite-directory.service.integration.spec.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { config as dotenvConfig } from "dotenv";
22
import { mock, MockProxy } from "jest-mock-extended";
33

4-
import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
5-
import { LogService } from "../../jslib/common/src/abstractions/log.service";
4+
import { I18nService } from "../../../jslib/common/src/abstractions/i18n.service";
5+
import { LogService } from "../../../jslib/common/src/abstractions/log.service";
66
import {
77
getGSuiteConfiguration,
88
getSyncConfiguration,
9-
} from "../../utils/google-workspace/config-fixtures";
10-
import { groupFixtures } from "../../utils/google-workspace/group-fixtures";
11-
import { userFixtures } from "../../utils/google-workspace/user-fixtures";
12-
import { DirectoryType } from "../enums/directoryType";
9+
} from "../../../utils/google-workspace/config-fixtures";
10+
import { groupFixtures } from "../../../utils/google-workspace/group-fixtures";
11+
import { userFixtures } from "../../../utils/google-workspace/user-fixtures";
12+
import { DirectoryType } from "../../enums/directoryType";
13+
import { StateService } from "../state.service";
1314

1415
import { GSuiteDirectoryService } from "./gsuite-directory.service";
15-
import { StateService } from "./state.service";
1616

1717
// These tests integrate with a test Google Workspace instance.
1818
// Credentials are located in the shared Bitwarden collection for Directory Connector testing.
@@ -24,10 +24,13 @@ dotenvConfig({ path: "utils/.env" });
2424
// These filters target integration test data.
2525
// These should return data that matches the user and group fixtures exactly.
2626
// There may be additional data present if not used.
27-
const INTEGRATION_USER_FILTER =
28-
"exclude:integration-user-a@bwrox.dev|orgUnitPath='/Integration testing'";
27+
const INTEGRATION_USER_FILTER = "|orgUnitPath='/Integration testing'";
2928
const INTEGRATION_GROUP_FILTER = "|name:Integration*";
3029

30+
// These tests are slow!
31+
// Increase the default timeout from 5s to 15s
32+
jest.setTimeout(15000);
33+
3134
describe("gsuiteDirectoryService", () => {
3235
let logService: MockProxy<LogService>;
3336
let i18nService: MockProxy<I18nService>;

src/services/gsuite-directory.service.ts renamed to src/services/directory-services/gsuite-directory.service.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { admin_directory_v1, google } from "googleapis";
44
import { I18nService } from "@/jslib/common/src/abstractions/i18n.service";
55
import { LogService } from "@/jslib/common/src/abstractions/log.service";
66

7-
import { StateService } from "../abstractions/state.service";
8-
import { DirectoryType } from "../enums/directoryType";
9-
import { GroupEntry } from "../models/groupEntry";
10-
import { GSuiteConfiguration } from "../models/gsuiteConfiguration";
11-
import { SyncConfiguration } from "../models/syncConfiguration";
12-
import { UserEntry } from "../models/userEntry";
13-
14-
import { BaseDirectoryService } from "./baseDirectory.service";
7+
import { StateService } from "../../abstractions/state.service";
8+
import { DirectoryType } from "../../enums/directoryType";
9+
import { GroupEntry } from "../../models/groupEntry";
10+
import { GSuiteConfiguration } from "../../models/gsuiteConfiguration";
11+
import { SyncConfiguration } from "../../models/syncConfiguration";
12+
import { UserEntry } from "../../models/userEntry";
13+
import { BaseDirectoryService } from "../baseDirectory.service";
14+
1515
import { IDirectoryService } from "./directory.service";
1616

1717
export class GSuiteDirectoryService extends BaseDirectoryService implements IDirectoryService {
@@ -253,7 +253,15 @@ export class GSuiteDirectoryService extends BaseDirectoryService implements IDir
253253
],
254254
});
255255

256-
await this.client.authorize();
256+
try {
257+
await this.client.authorize();
258+
} catch (error) {
259+
// Catch and rethrow this to sanitize any sensitive info (e.g. private key) in the error message
260+
this.logService.error(
261+
`Google Workspace authentication failed: ${error?.name || "Unknown error"}`,
262+
);
263+
throw new Error(this.i18nService.t("authenticationFailed"));
264+
}
257265

258266
this.authParams = {
259267
auth: this.client,

src/services/ldap-directory.service.integration.spec.ts renamed to src/services/directory-services/ldap-directory.service.integration.spec.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { mock, MockProxy } from "jest-mock-extended";
22

3-
import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
4-
import { LogService } from "../../jslib/common/src/abstractions/log.service";
5-
import { getLdapConfiguration, getSyncConfiguration } from "../../utils/openldap/config-fixtures";
6-
import { groupFixtures } from "../../utils/openldap/group-fixtures";
7-
import { userFixtures } from "../../utils/openldap/user-fixtures";
8-
import { DirectoryType } from "../enums/directoryType";
3+
import { I18nService } from "../../../jslib/common/src/abstractions/i18n.service";
4+
import { LogService } from "../../../jslib/common/src/abstractions/log.service";
5+
import {
6+
getLdapConfiguration,
7+
getSyncConfiguration,
8+
} from "../../../utils/openldap/config-fixtures";
9+
import { groupFixtures } from "../../../utils/openldap/group-fixtures";
10+
import { userFixtures } from "../../../utils/openldap/user-fixtures";
11+
import { DirectoryType } from "../../enums/directoryType";
12+
import { StateService } from "../state.service";
913

1014
import { LdapDirectoryService } from "./ldap-directory.service";
11-
import { StateService } from "./state.service";
1215

1316
// These tests integrate with the OpenLDAP docker image and seed data located in the openldap folder.
1417
// To run theses tests:

0 commit comments

Comments
 (0)