Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-13008] Add ldap integration tests #637

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3369f83
Add basic ldap user sync integration test
eliykat Sep 30, 2024
47d6b09
Add docker config and npm run script
eliykat Sep 30, 2024
5cdeef2
Refactor test setup
eliykat Sep 30, 2024
262c7a3
add phpldapadmin to docker compose
eliykat Sep 30, 2024
e093091
Tweak default config
eliykat Oct 1, 2024
b6952c9
Use full 20 users
eliykat Oct 1, 2024
b4f1090
Add groups test
eliykat Oct 1, 2024
ccce372
Fix json implementation
eliykat Oct 1, 2024
9776b41
Add user filter test
eliykat Oct 1, 2024
edf4795
Add groups filter test
eliykat Oct 1, 2024
c54b126
Add path tests
eliykat Oct 1, 2024
8b5f6ba
Group related tests
eliykat Oct 1, 2024
3be14fc
Stub out TODO tests
eliykat Oct 1, 2024
4ec99ff
Reorder directories and fix jest config
eliykat Oct 1, 2024
16c6ad2
Initial attempt at Github Workflow
eliykat Oct 2, 2024
8613d53
Use separate workflow
eliykat Oct 2, 2024
88d3f40
basic TLS test
eliykat Oct 2, 2024
fc29b00
Remove cert
eliykat Oct 2, 2024
31ca7a2
Remove TODO test
eliykat Oct 2, 2024
08f8f7b
Final tweaks
eliykat Oct 2, 2024
897fa64
Final tweaks
eliykat Oct 2, 2024
d8f4039
Add missing type
eliykat Oct 2, 2024
fc57f4e
Merge branch 'main' into ldap-integration-tests
eliykat Oct 2, 2024
c909c55
Address security feedback
eliykat Oct 2, 2024
5b5d7dd
Merge branch 'ldap-integration-tests' of https://github.com/bitwardenโ€ฆ
eliykat Oct 2, 2024
35fecbc
Attempt to fix CI
eliykat Oct 2, 2024
5d2616a
Add host interface specification
eliykat Oct 2, 2024
bda1104
Remove unnecessary typechecking
eliykat Oct 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/integration-test.yml
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether this should be separate to the existing test.yml file. As we add more integration tests it seems useful to keep it separate, but I'm not sure how this affects code coverage reporting.

As it stands this is mostly a copy/paste of test.yml.

We may want to use https://github.com/dorny/paths-filter down the line to selectively run different tests based on which directory service was changed. (The standard path filter only applies to the whole workflow, not steps within it.)

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Integration Testing

on:
workflow_dispatch:
push:
branches:
- "main"
paths:
- ".github/workflows/integration-test.yml" # this file
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment
pull_request:
paths:
- ".github/workflows/integration-test.yml" # this file
addisonbeck marked this conversation as resolved.
Show resolved Hide resolved
- "src/services/ldap-directory.service*" # we only have integration for LDAP testing at the moment

jobs:
check-test-secrets:
name: Check for test secrets
runs-on: ubuntu-22.04
outputs:
available: ${{ steps.check-test-secrets.outputs.available }}
permissions:
contents: read

steps:
- name: Check
id: check-test-secrets
run: |
if [ "${{ secrets.CODECOV_TOKEN }}" != '' ]; then
echo "available=true" >> $GITHUB_OUTPUT;
else
echo "available=false" >> $GITHUB_OUTPUT;
fi

testing:
name: Run tests
if: ${{ startsWith(github.head_ref, 'version_bump_') == false }}
runs-on: ubuntu-22.04
needs: check-test-secrets
permissions:
checks: write
contents: read
pull-requests: write

steps:
- name: Check out repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

- name: Get Node version
id: retrieve-node-version
run: |
NODE_NVMRC=$(cat .nvmrc)
NODE_VERSION=${NODE_NVMRC/v/''}
echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT

- name: Set up Node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}

- name: Install Node dependencies
run: npm ci

- name: Start OpenLDAP docker container
run: docker compose --project-directory utils/integration-tests --profile server up -d

- name: Run integration tests
run: |
npm run test:integration --coverage

- name: Report test results
uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1
if: ${{ needs.check-test-secrets.outputs.available == 'true' && !cancelled() }}
with:
name: Test Results
path: "junit.xml"
reporter: jest-junit
fail-on-error: true

- name: Upload coverage to codecov.io
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Upload results to codecov.io
uses: codecov/test-results-action@1b5b448b98e58ba90d1a1a1d9fcb72ca2263be46 # v1.0.0
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
"prettier": "prettier --write .",
"prepare": "husky install",
"test": "jest",
"test:watch": "jest --watch",
"test:watch:all": "jest --watchAll",
"test": "jest --testPathIgnorePatterns=.integration.spec.ts",
"test:watch": "jest --watch --testPathIgnorePatterns=.integration.spec.ts",
"test:watch:all": "jest --watchAll --testPathIgnorePatterns=.integration.spec.ts",
"test:integration": "jest .integration.spec.ts",
"test:integration:watch": "jest .integration.spec.ts --watch",
"test:types": "npx tsc --noEmit"
},
"devDependencies": {
Expand Down
36 changes: 36 additions & 0 deletions src/models/groupEntry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";

import { Entry } from "./entry";
import { UserEntry } from "./userEntry";

Expand All @@ -14,4 +16,38 @@

return this.name;
}

toJSON() {
return {

Check warning on line 21 in src/models/groupEntry.ts

View check run for this annotation

Codecov / codecov/patch

src/models/groupEntry.ts#L21

Added line #L21 was not covered by tests
name: this.name,
referenceId: this.referenceId,
externalId: this.externalId,
userMemberExternalIds:
this.userMemberExternalIds == null ? null : [...this.userMemberExternalIds],
groupMemberReferenceIds:
this.groupMemberReferenceIds == null ? null : [...this.groupMemberReferenceIds],
users: this.users?.map((u) => u.toJSON()),
};
}

static fromJSON(data: Jsonify<GroupEntry>) {
const result = new GroupEntry();
result.referenceId = data.referenceId;
result.externalId = data.externalId;
result.name = data.name;

if (data.userMemberExternalIds != null) {
result.userMemberExternalIds = new Set(data.userMemberExternalIds);
}

if (data.groupMemberReferenceIds != null) {
result.groupMemberReferenceIds = new Set(data.groupMemberReferenceIds);
}

if (data.users != null) {
result.users = data.users.map((u) => UserEntry.fromJSON(u));
}

return result;
}
}
24 changes: 24 additions & 0 deletions src/models/userEntry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";

import { Entry } from "./entry";

export class UserEntry extends Entry {
Expand All @@ -12,4 +14,26 @@

return this.email;
}

toJSON() {
return {

Check warning on line 19 in src/models/userEntry.ts

View check run for this annotation

Codecov / codecov/patch

src/models/userEntry.ts#L19

Added line #L19 was not covered by tests
referenceId: this.referenceId,
externalId: this.externalId,
email: this.email,
disabled: this.disabled,
deleted: this.deleted,
};
}

static fromJSON(data: Jsonify<UserEntry>) {
const result = new UserEntry();
result.referenceId = data.referenceId;
result.externalId = data.externalId;

result.email = data.email;
result.disabled = data.disabled;
result.deleted = data.deleted;

return result;
}
}
191 changes: 191 additions & 0 deletions src/services/ldap-directory.service.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { mock, MockProxy } from "jest-mock-extended";

Check warning on line 1 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L1

Added line #L1 was not covered by tests

import { I18nService } from "../../jslib/common/src/abstractions/i18n.service";
import { LogService } from "../../jslib/common/src/abstractions/log.service";
import { groupFixtures } from "../../utils/integration-tests/group-fixtures";
import { userFixtures } from "../../utils/integration-tests/user-fixtures";
import { DirectoryType } from "../enums/directoryType";

Check warning on line 7 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L5-L7

Added lines #L5 - L7 were not covered by tests
import { LdapConfiguration } from "../models/ldapConfiguration";
import { SyncConfiguration } from "../models/syncConfiguration";

import { LdapDirectoryService } from "./ldap-directory.service";

Check warning on line 11 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L11

Added line #L11 was not covered by tests
import { StateService } from "./state.service";

// These tests integrate with the OpenLDAP docker image and seed data located in utils/integration-tests.
// To start the docker container for local testing:
// cd utils/integration-tests
// docker compose --profile server up -d
// Once the docker container is running, these tests can be run using:
// npm run test:integration:watch
// The docker compose config also includes a PhpLDAPAdmin container for inspecting the LDAP directory contents.

describe("ldapDirectoryService", () => {

Check warning on line 22 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L22

Added line #L22 was not covered by tests
let logService: MockProxy<LogService>;
let i18nService: MockProxy<I18nService>;
let stateService: MockProxy<StateService>;

let directoryService: LdapDirectoryService;

beforeEach(() => {
logService = mock();
i18nService = mock();
stateService = mock();

Check warning on line 32 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L29-L32

Added lines #L29 - L32 were not covered by tests
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future refactor: it would be good to remove stateService from the directory service implementations and pass in their config as arguments instead.


stateService.getDirectoryType.mockResolvedValue(DirectoryType.Ldap);
stateService.getLastUserSync.mockResolvedValue(null); // do not filter results by last modified date
i18nService.t.mockImplementation((id) => id); // passthrough implementation for any error messages

Check warning on line 36 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L34-L36

Added lines #L34 - L36 were not covered by tests

directoryService = new LdapDirectoryService(logService, i18nService, stateService);

Check warning on line 38 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L38

Added line #L38 was not covered by tests
});

it("gets users and groups without TLS", async () => {
stateService.getDirectory

Check warning on line 42 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L41-L42

Added lines #L41 - L42 were not covered by tests
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(getSyncConfiguration({ groups: true, users: true }));

Check warning on line 45 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L45

Added line #L45 was not covered by tests

const result = await directoryService.getEntries(true, true);
expect(result).toEqual([groupFixtures, userFixtures]);

Check warning on line 48 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L47-L48

Added lines #L47 - L48 were not covered by tests
});

it("gets users and groups with TLS", async () => {
stateService.getDirectory.calledWith(DirectoryType.Ldap).mockResolvedValue(

Check warning on line 52 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L51-L52

Added lines #L51 - L52 were not covered by tests
getLdapConfiguration({
ssl: true,
startTls: true,
sslAllowUnauthorized: true, // TODO: this could be more robust if we configured certs correctly
}),
Comment on lines +53 to +57
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a half baked SSL config at the moment, I just wanted something in place. Ideally we'd specify a certificate and not use sslAllowUnauthorized.

);
stateService.getSync.mockResolvedValue(getSyncConfiguration({ groups: true, users: true }));

Check warning on line 59 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L59

Added line #L59 was not covered by tests

const result = await directoryService.getEntries(true, true);
expect(result).toEqual([groupFixtures, userFixtures]);

Check warning on line 62 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L61-L62

Added lines #L61 - L62 were not covered by tests
});

describe("users", () => {
it("respects the users path", async () => {
stateService.getDirectory

Check warning on line 67 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L65-L67

Added lines #L65 - L67 were not covered by tests
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(

Check warning on line 70 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L70

Added line #L70 was not covered by tests
getSyncConfiguration({
users: true,
userPath: "ou=Human Resources",
}),
);

// These users are in the Human Resources ou
const hrUsers = userFixtures.filter(

Check warning on line 78 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L78

Added line #L78 was not covered by tests
(u) =>
u.referenceId === "cn=Roland Dyke,ou=Human Resources,dc=bitwarden,dc=com" ||
u.referenceId === "cn=Charin Goulfine,ou=Human Resources,dc=bitwarden,dc=com" ||
u.referenceId === "cn=Angelle Guarino,ou=Human Resources,dc=bitwarden,dc=com",
);

const result = await directoryService.getEntries(true, true);
expect(result[1]).toEqual(expect.arrayContaining(hrUsers));
expect(result[1].length).toEqual(hrUsers.length);

Check warning on line 87 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L85-L87

Added lines #L85 - L87 were not covered by tests
});

it("filters users", async () => {
stateService.getDirectory

Check warning on line 91 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L90-L91

Added lines #L90 - L91 were not covered by tests
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(

Check warning on line 94 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L94

Added line #L94 was not covered by tests
getSyncConfiguration({ users: true, userFilter: "(cn=Roland Dyke)" }),
);

const roland = userFixtures.find(
(u) => u.referenceId === "cn=Roland Dyke,ou=Human Resources,dc=bitwarden,dc=com",

Check warning on line 99 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L98-L99

Added lines #L98 - L99 were not covered by tests
);
const result = await directoryService.getEntries(true, true);
expect(result).toEqual([undefined, [roland]]);

Check warning on line 102 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L101-L102

Added lines #L101 - L102 were not covered by tests
});
});

describe("groups", () => {
it("respects the groups path", async () => {
stateService.getDirectory

Check warning on line 108 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L106-L108

Added lines #L106 - L108 were not covered by tests
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(

Check warning on line 111 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L111

Added line #L111 was not covered by tests
getSyncConfiguration({
groups: true,
groupPath: "ou=Janitorial",
}),
);

// These groups are in the Janitorial ou
const janitorialGroups = groupFixtures.filter((g) => g.name === "Cleaners");

Check warning on line 119 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L119

Added line #L119 was not covered by tests

const result = await directoryService.getEntries(true, true);
expect(result).toEqual([janitorialGroups, undefined]);

Check warning on line 122 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L121-L122

Added lines #L121 - L122 were not covered by tests
});

it("filters groups", async () => {
stateService.getDirectory

Check warning on line 126 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L125-L126

Added lines #L125 - L126 were not covered by tests
.calledWith(DirectoryType.Ldap)
.mockResolvedValue(getLdapConfiguration());
stateService.getSync.mockResolvedValue(

Check warning on line 129 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L129

Added line #L129 was not covered by tests
getSyncConfiguration({ groups: true, groupFilter: "(cn=Red Team)" }),
);

const redTeam = groupFixtures.find(
(u) => u.referenceId === "cn=Red Team,dc=bitwarden,dc=com",

Check warning on line 134 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L133-L134

Added lines #L133 - L134 were not covered by tests
);
const result = await directoryService.getEntries(true, true);
expect(result).toEqual([[redTeam], undefined]);

Check warning on line 137 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L136-L137

Added lines #L136 - L137 were not covered by tests
});
});
});

/**
* @returns a basic ldap configuration without TLS/SSL enabled. Can be overridden by passing in a partial configuration.
*/
const getLdapConfiguration = (config?: Partial<LdapConfiguration>): LdapConfiguration => ({

Check warning on line 145 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L145

Added line #L145 was not covered by tests
ssl: false,
startTls: false,
tlsCaPath: null,
sslAllowUnauthorized: false,
sslCertPath: null,
sslKeyPath: null,
sslCaPath: null,
hostname: "127.0.0.1",
port: 1389,
domain: null,
rootPath: "dc=bitwarden,dc=com",
currentUser: false,
username: "cn=admin,dc=bitwarden,dc=com",
password: "admin",
ad: false,
pagedSearch: false,
...(config ?? {}),
});

/**
* @returns a basic sync configuration. Can be overridden by passing in a partial configuration.
*/
const getSyncConfiguration = (config?: Partial<SyncConfiguration>): SyncConfiguration => ({

Check warning on line 168 in src/services/ldap-directory.service.integration.spec.ts

View check run for this annotation

Codecov / codecov/patch

src/services/ldap-directory.service.integration.spec.ts#L168

Added line #L168 was not covered by tests
users: false,
groups: false,
interval: 5,
userFilter: null,
groupFilter: null,
removeDisabled: false,
overwriteExisting: false,
largeImport: false,
// Ldap properties
groupObjectClass: "posixGroup",
userObjectClass: "person",
groupPath: null,
userPath: null,
groupNameAttribute: "cn",
userEmailAttribute: "mail",
memberAttribute: "memberUid",
useEmailPrefixSuffix: false,
emailPrefixAttribute: "sAMAccountName",
emailSuffix: null,
creationDateAttribute: "whenCreated",
revisionDateAttribute: "whenChanged",
...(config ?? {}),
});
1 change: 1 addition & 0 deletions utils/integration-tests/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
COMPOSE_PROJECT_NAME=directory-connector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It gives a nice name to the grouping in docker desktop:

Screenshot 2024-10-07 at 1 31 00โ€ฏPM

Copy link
Contributor

@addisonbeck addisonbeck Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make more sense to set this in docker-compose.yml? Docker's documentation says it will take the top level name value here.

Also, if you're planning to use this as the scaffolding location for local development containers it doesn't make sense to put it in the integration-tests folder. I do think it's reasonable to put docker-compose.yml (and .env if you keep it) in project root.

Loading
Loading