mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com> Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
241 lines
7.3 KiB
TypeScript
241 lines
7.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
APPLY_STATUS,
|
|
ApplyStatus,
|
|
DiscoveryResult,
|
|
} from "@/types/organizations";
|
|
|
|
import {
|
|
buildAccountLookup,
|
|
buildOrgTreeData,
|
|
getOuIdsForSelectedAccounts,
|
|
getSelectableAccountIds,
|
|
getSelectableAccountIdsForTarget,
|
|
} from "./organizations.adapter";
|
|
|
|
const discoveryFixture: DiscoveryResult = {
|
|
roots: [
|
|
{
|
|
id: "r-root",
|
|
arn: "arn:aws:organizations::123:root/o-example/r-root",
|
|
name: "Root",
|
|
policy_types: [],
|
|
},
|
|
],
|
|
organizational_units: [
|
|
{
|
|
id: "ou-parent",
|
|
name: "Parent OU",
|
|
arn: "arn:aws:organizations::123:ou/o-example/ou-parent",
|
|
parent_id: "r-root",
|
|
},
|
|
{
|
|
id: "ou-child",
|
|
name: "Child OU",
|
|
arn: "arn:aws:organizations::123:ou/o-example/ou-child",
|
|
parent_id: "ou-parent",
|
|
},
|
|
],
|
|
accounts: [
|
|
{
|
|
id: "111111111111",
|
|
arn: "arn:aws:organizations::123:account/o-example/111111111111",
|
|
name: "App Account",
|
|
email: "app@example.com",
|
|
status: "ACTIVE",
|
|
joined_method: "CREATED",
|
|
joined_timestamp: "2024-01-01T00:00:00Z",
|
|
parent_id: "ou-child",
|
|
registration: {
|
|
provider_exists: false,
|
|
provider_id: null,
|
|
organization_relation: "link_required",
|
|
organizational_unit_relation: "link_required",
|
|
provider_secret_state: "will_create",
|
|
apply_status: APPLY_STATUS.READY,
|
|
blocked_reasons: [],
|
|
},
|
|
},
|
|
{
|
|
id: "222222222222",
|
|
arn: "arn:aws:organizations::123:account/o-example/222222222222",
|
|
name: "Security Account",
|
|
email: "security@example.com",
|
|
status: "ACTIVE",
|
|
joined_method: "CREATED",
|
|
joined_timestamp: "2024-01-01T00:00:00Z",
|
|
parent_id: "ou-parent",
|
|
registration: {
|
|
provider_exists: false,
|
|
provider_id: null,
|
|
organization_relation: "link_required",
|
|
organizational_unit_relation: "link_required",
|
|
provider_secret_state: "manual_required",
|
|
apply_status: APPLY_STATUS.BLOCKED,
|
|
blocked_reasons: ["role_missing"],
|
|
},
|
|
},
|
|
{
|
|
id: "333333333333",
|
|
arn: "arn:aws:organizations::123:account/o-example/333333333333",
|
|
name: "Legacy Account",
|
|
email: "legacy@example.com",
|
|
status: "ACTIVE",
|
|
joined_method: "INVITED",
|
|
joined_timestamp: "2024-01-01T00:00:00Z",
|
|
parent_id: "r-root",
|
|
},
|
|
],
|
|
};
|
|
|
|
describe("buildOrgTreeData", () => {
|
|
it("builds nested tree structure and marks blocked accounts as disabled", () => {
|
|
// Given / When
|
|
const treeData = buildOrgTreeData(discoveryFixture);
|
|
|
|
// Then
|
|
expect(treeData).toHaveLength(2);
|
|
expect(treeData.map((node) => node.id)).toEqual(
|
|
expect.arrayContaining(["ou-parent", "333333333333"]),
|
|
);
|
|
|
|
const parentOuNode = treeData.find((node) => node.id === "ou-parent");
|
|
expect(parentOuNode).toBeDefined();
|
|
expect(parentOuNode?.children?.map((node) => node.id)).toEqual(
|
|
expect.arrayContaining(["ou-child", "222222222222"]),
|
|
);
|
|
|
|
const blockedAccount = parentOuNode?.children?.find(
|
|
(node) => node.id === "222222222222",
|
|
);
|
|
expect(blockedAccount?.disabled).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getSelectableAccountIds", () => {
|
|
it("returns all accounts except explicitly blocked ones", () => {
|
|
const selectableIds = getSelectableAccountIds(discoveryFixture);
|
|
|
|
expect(selectableIds).toEqual(["111111111111", "333333333333"]);
|
|
});
|
|
|
|
it("excludes accounts with explicit non-ready status values", () => {
|
|
const discoveryWithUnexpectedStatus = {
|
|
...discoveryFixture,
|
|
accounts: [
|
|
...discoveryFixture.accounts,
|
|
{
|
|
id: "444444444444",
|
|
arn: "arn:aws:organizations::123:account/o-example/444444444444",
|
|
name: "Pending Account",
|
|
email: "pending@example.com",
|
|
status: "ACTIVE",
|
|
joined_method: "CREATED",
|
|
joined_timestamp: "2024-01-01T00:00:00Z",
|
|
parent_id: "r-root",
|
|
registration: {
|
|
provider_exists: false,
|
|
provider_id: null,
|
|
organization_relation: "link_required",
|
|
organizational_unit_relation: "link_required",
|
|
provider_secret_state: "will_create",
|
|
apply_status: "pending" as unknown as ApplyStatus,
|
|
blocked_reasons: [],
|
|
},
|
|
},
|
|
],
|
|
} satisfies DiscoveryResult;
|
|
|
|
const selectableIds = getSelectableAccountIds(
|
|
discoveryWithUnexpectedStatus,
|
|
);
|
|
|
|
expect(selectableIds).toEqual(["111111111111", "333333333333"]);
|
|
});
|
|
});
|
|
|
|
describe("buildAccountLookup", () => {
|
|
it("creates a lookup map for all discovered accounts", () => {
|
|
const lookup = buildAccountLookup(discoveryFixture);
|
|
|
|
expect(lookup.get("111111111111")?.name).toBe("App Account");
|
|
expect(lookup.get("333333333333")?.name).toBe("Legacy Account");
|
|
expect(lookup.size).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("getSelectableAccountIdsForTarget", () => {
|
|
it("scopes selection to accounts under a target OU, including nested OUs", () => {
|
|
// ou-parent contains ou-child (holds 111...) and the blocked 222...
|
|
const scoped = getSelectableAccountIdsForTarget(
|
|
discoveryFixture,
|
|
"ou-parent",
|
|
);
|
|
|
|
// Only the selectable descendant is returned; blocked 222... is excluded,
|
|
// and 333... (under the root, outside the OU) is not included.
|
|
expect(scoped).toEqual(["111111111111"]);
|
|
});
|
|
|
|
it("scopes selection to a leaf OU", () => {
|
|
const scoped = getSelectableAccountIdsForTarget(
|
|
discoveryFixture,
|
|
"ou-child",
|
|
);
|
|
|
|
expect(scoped).toEqual(["111111111111"]);
|
|
});
|
|
|
|
it("includes the deployment account even when it lives outside the target OU", () => {
|
|
// Deployment (management) account 333... sits under the root, but gets the
|
|
// role via DeployLocalRole, so it must be pre-selected alongside the OU.
|
|
const scoped = getSelectableAccountIdsForTarget(
|
|
discoveryFixture,
|
|
"ou-child",
|
|
"333333333333",
|
|
);
|
|
|
|
expect(scoped).toEqual(["111111111111", "333333333333"]);
|
|
});
|
|
|
|
it("does not include a deployment account that is not selectable", () => {
|
|
// 222... is blocked, so even as the deployment account it stays unselected.
|
|
const scoped = getSelectableAccountIdsForTarget(
|
|
discoveryFixture,
|
|
"ou-child",
|
|
"222222222222",
|
|
);
|
|
|
|
expect(scoped).toEqual(["111111111111"]);
|
|
});
|
|
|
|
it("returns every selectable account for a root target (whole organization)", () => {
|
|
const scoped = getSelectableAccountIdsForTarget(discoveryFixture, "r-root");
|
|
|
|
expect(scoped).toEqual(["111111111111", "333333333333"]);
|
|
});
|
|
|
|
it("falls back to all selectable accounts for an empty or unknown target", () => {
|
|
expect(getSelectableAccountIdsForTarget(discoveryFixture, "")).toEqual([
|
|
"111111111111",
|
|
"333333333333",
|
|
]);
|
|
expect(
|
|
getSelectableAccountIdsForTarget(discoveryFixture, "ou-does-not-exist"),
|
|
).toEqual(["111111111111", "333333333333"]);
|
|
});
|
|
});
|
|
|
|
describe("getOuIdsForSelectedAccounts", () => {
|
|
it("collects all ancestor OUs for selected accounts without duplicates", () => {
|
|
const ouIds = getOuIdsForSelectedAccounts(discoveryFixture, [
|
|
"111111111111",
|
|
"222222222222",
|
|
]);
|
|
|
|
expect(ouIds).toEqual(expect.arrayContaining(["ou-parent", "ou-child"]));
|
|
expect(ouIds.length).toBe(2);
|
|
});
|
|
});
|