Skip to content

Commit

Permalink
feat: exported action items are also copied on imported models
Browse files Browse the repository at this point in the history
  • Loading branch information
Tethik committed Dec 14, 2023
1 parent 1f87897 commit efb200f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 13 deletions.
67 changes: 55 additions & 12 deletions core/src/action-items/ActionItemHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ActionItemHandler } from "./ActionItemHandler.js";
import { DummyActionItemExporter } from "./DummyActionItemExporter.js";
import { ActionItem } from "../data/threats/ActionItem.js";
import { ExportResult } from "./ActionItemExporter.js";
import { createSampleModel } from "../test-util/model.js";

class OtherDummyActionItemExporter extends DummyActionItemExporter {
key = "other-dummy";
Expand All @@ -32,7 +33,7 @@ class OtherDummyActionItemExporter extends DummyActionItemExporter {

describe("ActionItemHandler implementation", () => {
let dal: DataAccessLayer;
let model: Model;
let modelId: string;
let threatId: string;
let review: Review;

Expand All @@ -43,17 +44,24 @@ describe("ActionItemHandler implementation", () => {

beforeEach(async () => {
await _deleteAllTheThings(dal);
model = new Model("some-system-id", "some-version", "root");
model.data = { components: [], dataFlows: [] };
model.id = await dal.modelService.create(model);
modelId = await createSampleModel(dal);

const model = await dal.modelService.getById(modelId);

threatId = await dal.threatService.create(
new Threat("title", "desc", model.id!, randomUUID(), "root")
new Threat(
"title",
"desc",
modelId,
model?.data.components[0].id!,
"root"
)
);
await dal.threatService.update(model.id, threatId, {
await dal.threatService.update(modelId, threatId, {
severity: ThreatSeverity.High,
isActionItem: true,
});
review = new Review(model.id!, "root");
review = new Review(modelId, "root");
await dal.reviewService.create(review);
});

Expand All @@ -66,7 +74,7 @@ describe("ActionItemHandler implementation", () => {
const handler = new ActionItemHandler(dal);
handler.attachExporter(new DummyActionItemExporter());
await handler.onReviewApproved(review);
const actionItems = await dal.threatService.listActionItems(model.id!);
const actionItems = await dal.threatService.listActionItems(modelId);
expect(actionItems).toHaveLength(1);
expect(actionItems[0].threat.id).toEqual(threatId);
expect(actionItems[0].threat.severity).toEqual(ThreatSeverity.High);
Expand All @@ -78,7 +86,7 @@ describe("ActionItemHandler implementation", () => {
const handler = new ActionItemHandler(dal);
handler.attachExporter(new DummyActionItemExporter());
await handler.onReviewApproved(review);
const actionItems = await dal.threatService.listActionItems(model.id!);
const actionItems = await dal.threatService.listActionItems(modelId);
expect(actionItems).toHaveLength(1);
expect(actionItems[0].threat.id).toEqual(threatId);
expect(actionItems[0].threat.severity).toEqual(ThreatSeverity.High);
Expand All @@ -99,7 +107,7 @@ describe("ActionItemHandler implementation", () => {
handler.attachExporter(new DummyActionItemExporter());
handler.attachExporter(new OtherDummyActionItemExporter());
await handler.onReviewApproved(review);
const actionItems = await dal.threatService.listActionItems(model.id!);
const actionItems = await dal.threatService.listActionItems(modelId);
expect(actionItems).toHaveLength(1);
expect(actionItems[0].threat.id).toEqual(threatId);
expect(actionItems[0].threat.severity).toEqual(ThreatSeverity.High);
Expand All @@ -114,7 +122,7 @@ describe("ActionItemHandler implementation", () => {
const other = new OtherDummyActionItemExporter();
handler.attachExporter(other);
await handler.onReviewApproved(review);
const actionItems = await dal.threatService.listActionItems(model.id!);
const actionItems = await dal.threatService.listActionItems(modelId);
expect(actionItems).toHaveLength(1);
expect(actionItems[0].threat.id).toEqual(threatId);
expect(actionItems[0].threat.severity).toEqual(ThreatSeverity.High);
Expand All @@ -125,7 +133,7 @@ describe("ActionItemHandler implementation", () => {
// change the url
other.setUrl("changed");
await handler.onReviewApproved(review);
const actionItems2 = await dal.threatService.listActionItems(model.id!);
const actionItems2 = await dal.threatService.listActionItems(modelId);
expect(actionItems2).toHaveLength(1);
expect(actionItems2[0].threat.id).toEqual(threatId);
expect(actionItems2[0].threat.severity).toEqual(ThreatSeverity.High);
Expand All @@ -135,6 +143,41 @@ describe("ActionItemHandler implementation", () => {
expect(actionItems2[0].exports[1].linkedURL).toBe("changed");
});

it("should still keep action item exports on model import", async () => {
const handler = new ActionItemHandler(dal);
handler.attachExporter(new DummyActionItemExporter());
const other = new OtherDummyActionItemExporter();
handler.attachExporter(other);
await handler.onReviewApproved(review);

const actionItems = await dal.threatService.listActionItems(modelId);
expect(actionItems).toHaveLength(1);
expect(actionItems[0].threat.id).toEqual(threatId);
expect(actionItems[0].threat.severity).toEqual(ThreatSeverity.High);
expect(actionItems[0].exports).toHaveLength(2);
expect(actionItems[0].exports[0].exporterKey).toBe("dummy");
expect(actionItems[0].exports[1].exporterKey).toBe("other-dummy");

const modelId2 = await dal.modelService.copy(
modelId,
new Model("other", "new", "root")
);

expect(modelId).not.toEqual(modelId2);
expect(modelId).not.toBeNull();

const threats = await dal.threatService.list(modelId2!);
expect(threats).toHaveLength(1);

const actionItems2 = await dal.threatService.listActionItems(modelId2!);
expect(actionItems2).toHaveLength(1);
expect(actionItems2[0].threat.id).not.toEqual(threatId); // Thread ID gets overwritten during import
expect(actionItems2[0].threat.severity).toEqual(ThreatSeverity.High);
expect(actionItems2[0].exports).toHaveLength(2);
expect(actionItems2[0].exports[0].exporterKey).toBe("dummy");
expect(actionItems2[0].exports[1].exporterKey).toBe("other-dummy");
});

afterAll(async () => {
await dal.pool.end();
});
Expand Down
18 changes: 18 additions & 0 deletions core/src/data/models/ModelDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ export class ModelDataService extends EventEmitter {
AND deleted_at IS NULL;
`;

const queryExportedActionItems = `
INSERT INTO exported_action_items (
threat_id, exporter_key, url, created_at, updated_at
)
SELECT $1::uuid as threat_id,
exporter_key,
url,
created_at,
updated_at
FROM exported_action_items
WHERE threat_id = $2::uuid;
`;

const queryControls = `
INSERT INTO controls (
id, model_id, component_id, title, description, in_place, created_by, suggestion_id, created_at
Expand Down Expand Up @@ -310,6 +323,11 @@ export class ModelDataService extends EventEmitter {
: null,
threat.id,
]);

await client.query(queryExportedActionItems, [
uuid.get(threat.id!),
threat.id,
]);
}

for (const control of controls) {
Expand Down
20 changes: 19 additions & 1 deletion core/src/test-util/model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import { randomUUID } from "crypto";
import { DataAccessLayer } from "../data/dal.js";
import Model from "../data/models/Model.js";
import { sampleOwnedSystem } from "./sampleOwnedSystem.js";

export async function createSampleModel(dal: DataAccessLayer) {
const model = new Model(sampleOwnedSystem.id, "some-version", "root");
model.data = { components: [], dataFlows: [] };
const component1Id = randomUUID();
const component2Id = randomUUID();
const datafFlowId = randomUUID();
model.data = {
components: [
{ id: component1Id, x: 0, y: 0, type: "ee", name: "omegalul" },
{ id: component2Id, x: 1, y: 1, type: "ds", name: "hello" },
],
dataFlows: [
{
id: datafFlowId,
endComponent: { id: component1Id },
startComponent: { id: component2Id },
points: [0, 0],
bidirectional: false,
},
],
};
const modelId = await dal.modelService.create(model);
return modelId;
}

0 comments on commit efb200f

Please sign in to comment.