-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: scenario trends sharing (#272)
- Loading branch information
Showing
25 changed files
with
974 additions
and
312 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const { PgLiteral } = require("node-pg-migrate") | ||
exports.up = (pgm) => { | ||
pgm.createTable({ schema: "jtl", name: "scenario_share_tokens" }, { | ||
id: { | ||
type: "uuid", | ||
"default": new PgLiteral("uuid_generate_v4()"), | ||
notNull: true, | ||
primaryKey: true, | ||
}, | ||
token: { | ||
type: "text", | ||
notNull: true, | ||
"default": null, | ||
}, | ||
note: { | ||
type: "varchar(200)", | ||
notNull: true, | ||
"default": null, | ||
}, | ||
created_by: { | ||
type: "uuid", | ||
"default": null, | ||
references: { schema: "jtl", name: "users" }, | ||
notNull: true, | ||
}, | ||
scenario_id: { | ||
type: "uuid", | ||
"default": null, | ||
references: { schema: "jtl", name: "scenario" }, | ||
notNull: true, | ||
}, | ||
}) | ||
} |
71 changes: 71 additions & 0 deletions
71
src/server/controllers/item/share-tokens/delete-item-share-token-controller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { IGetUserAuthInfoRequest } from "../../../middleware/request.model" | ||
import { Response } from "express" | ||
import { AllowedRoles } from "../../../middleware/authorization-middleware" | ||
import { v4 as uuidv4 } from "uuid" | ||
import { db } from "../../../../db/db" | ||
import { deleteItemShareTokenController } from "./delete-item-share-token-controller" | ||
|
||
jest.mock("../../../../db/db") | ||
|
||
|
||
const mockResponse = () => { | ||
const res: Partial<Response> = {} | ||
res.send = jest.fn().mockReturnValue(res) | ||
res.status = jest.fn().mockReturnValue(res) | ||
res.json = jest.fn().mockReturnValue(res) | ||
return res | ||
} | ||
|
||
describe("deleteItemShareTokenController", () => { | ||
it("should be able to delete only my token whe user role is operator", async () => { | ||
|
||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/items"), | ||
"deleteMyShareToken") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", itemId: "itemId", tokenId: "my-share-token" }, | ||
user: { role: AllowedRoles.Operator, userId: uuidv4() }, | ||
}; | ||
(db.none as any).mockReturnValueOnce() | ||
|
||
await deleteItemShareTokenController( | ||
request as unknown as IGetUserAuthInfoRequest, | ||
response as unknown as Response) | ||
|
||
expect(querySpy).toHaveBeenCalledTimes(1) | ||
expect(querySpy) | ||
.toHaveBeenLastCalledWith( | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
request.params.itemId, | ||
request.params.tokenId, | ||
request.user.userId) | ||
expect(response.send).toHaveBeenCalledTimes(1) | ||
}) | ||
it("should be able to delete any token whe user role is admin", async () => { | ||
|
||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/items"), | ||
"deleteShareToken") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", itemId: "itemId", tokenId: "my-share-token" }, | ||
user: { role: AllowedRoles.Admin, userId: uuidv4() }, | ||
}; | ||
(db.none as any).mockReturnValueOnce() | ||
|
||
await deleteItemShareTokenController( | ||
request as unknown as IGetUserAuthInfoRequest, | ||
response as unknown as Response) | ||
|
||
expect(querySpy).toHaveBeenCalledTimes(1) | ||
expect(querySpy) | ||
.toHaveBeenLastCalledWith( | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
request.params.itemId, | ||
request.params.tokenId | ||
) | ||
expect(response.send).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 48 additions & 15 deletions
63
src/server/controllers/item/share-tokens/get-item-share-tokens-controller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,57 @@ | ||
import { getItemLinksController } from "./get-item-share-tokens-controller" | ||
import { Response } from "express" | ||
import { IGetUserAuthInfoRequest } from "../../../middleware/request.model" | ||
import { AllowedRoles } from "../../../middleware/authorization-middleware" | ||
import { db } from "../../../../db/db" | ||
import { StatusCode } from "../../../utils/status-code" | ||
|
||
jest.mock("../../../../db/db") | ||
const mockResponse = () => { | ||
const res: Partial<Response> = {} | ||
res.json = jest.fn().mockReturnValue(res) | ||
res.status = jest.fn().mockReturnValue(res) | ||
return res | ||
const res: Partial<Response> = {} | ||
res.json = jest.fn().mockReturnValue(res) | ||
res.status = jest.fn().mockReturnValue(res) | ||
res.send = jest.fn().mockReturnValue(res) | ||
return res | ||
} | ||
|
||
describe("getItemLinksController", () => { | ||
it("should fetch data from db", async () => { | ||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/items"), "selectShareTokens") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", itemId: "id" }, | ||
user: { userId: "userId" }, | ||
} | ||
await getItemLinksController(request as unknown as IGetUserAuthInfoRequest, response as unknown as Response) | ||
expect(querySpy).toHaveBeenCalledTimes(1) | ||
expect(response.json).toHaveBeenCalledTimes(1) | ||
}) | ||
it("should only my tokens when user role is operator", async () => { | ||
const tokenData = [{ id: "1", token: "tokenId", name: "test token" }] | ||
|
||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/items"), "selectOnlyMyShareTokens") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", itemId: "id" }, | ||
user: { userId: "userId", role: AllowedRoles.Operator }, | ||
}; | ||
(db.manyOrNone as any).mockResolvedValueOnce(tokenData) | ||
await getItemLinksController(request as unknown as IGetUserAuthInfoRequest, response as unknown as Response) | ||
expect(querySpy).toHaveBeenNthCalledWith(1, | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
request.params.itemId, | ||
request.user.userId | ||
) | ||
expect(response.send).toHaveBeenNthCalledWith(1, StatusCode.Ok) | ||
expect(response.json).toHaveBeenNthCalledWith(1, tokenData) | ||
}) | ||
|
||
it("should return all tokens when user role is admin", async () => { | ||
const tokenData = [{ id: "1", token: "tokenId", name: "test token" }] | ||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/items"), "selectShareTokens") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", itemId: "id" }, | ||
user: { userId: "userId", role: AllowedRoles.Admin }, | ||
}; | ||
(db.manyOrNone as any).mockResolvedValueOnce(tokenData) | ||
await getItemLinksController(request as unknown as IGetUserAuthInfoRequest, response as unknown as Response) | ||
expect(querySpy).toHaveBeenNthCalledWith(1, | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
request.params.itemId | ||
) | ||
expect(response.json).toHaveBeenNthCalledWith(1, tokenData) | ||
expect(response.status).toHaveBeenNthCalledWith(1, StatusCode.Ok) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
src/server/controllers/scenario/share-token/create-scenario-share-token-controller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { IGetUserAuthInfoRequest } from "../../../middleware/request.model" | ||
import { Response } from "express" | ||
import { AllowedRoles } from "../../../middleware/authorization-middleware" | ||
import { v4 as uuidv4 } from "uuid" | ||
import { createScenarioShareTokenController } from "./create-scenario-share-token-controller" | ||
import { StatusCode } from "../../../utils/status-code" | ||
import { db } from "../../../../db/db" | ||
|
||
jest.mock("../../../../db/db") | ||
|
||
const mockResponse = () => { | ||
const res: Partial<Response> = {} | ||
res.send = jest.fn().mockReturnValue(res) | ||
res.status = jest.fn().mockReturnValue(res) | ||
res.json = jest.fn().mockReturnValue(res) | ||
return res | ||
} | ||
|
||
describe("createScenarioShareTokenController", () => { | ||
it("should generate token and save it into db", async () => { | ||
const mockToken = "test-token-id" | ||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/scenario"), | ||
"createScenarioShareToken") | ||
const tokenSpy = jest.spyOn(require("../../item/utils/generateShareToken"), | ||
"generateShareToken") | ||
tokenSpy.mockReturnValueOnce(mockToken); | ||
(db.none as any).mockReturnValueOnce() | ||
|
||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", shareTokenId: "my-share-token" }, | ||
body: { note: "my-note" }, | ||
user: { role: AllowedRoles.Operator, userId: uuidv4() }, | ||
} | ||
|
||
await createScenarioShareTokenController( | ||
request as unknown as IGetUserAuthInfoRequest, | ||
response as unknown as Response) | ||
|
||
expect(querySpy).toHaveBeenNthCalledWith(1, | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
mockToken, | ||
request.user.userId, | ||
request.body.note) | ||
expect(response.status).toHaveBeenNthCalledWith(1, StatusCode.Created) | ||
expect(response.json).toHaveBeenNthCalledWith(1, { token: mockToken }) | ||
}) | ||
}) |
15 changes: 15 additions & 0 deletions
15
src/server/controllers/scenario/share-token/create-scenario-share-token-controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Response } from "express" | ||
import { IGetUserAuthInfoRequest } from "../../../middleware/request.model" | ||
import { generateShareToken } from "../../item/utils/generateShareToken" | ||
import { db } from "../../../../db/db" | ||
import { createScenarioShareToken } from "../../../queries/scenario" | ||
import { StatusCode } from "../../../utils/status-code" | ||
|
||
export const createScenarioShareTokenController = async (req: IGetUserAuthInfoRequest, res: Response) => { | ||
const { projectName, scenarioName } = req.params | ||
const { user } = req | ||
const { note } = req.body | ||
const token = generateShareToken() | ||
await db.none(createScenarioShareToken(projectName, scenarioName, token, user.userId, note)) | ||
res.status(StatusCode.Created).json({ token }) | ||
} |
71 changes: 71 additions & 0 deletions
71
src/server/controllers/scenario/share-token/delete-scenario-share-token-controller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { IGetUserAuthInfoRequest } from "../../../middleware/request.model" | ||
import { Response } from "express" | ||
import { AllowedRoles } from "../../../middleware/authorization-middleware" | ||
import { v4 as uuidv4 } from "uuid" | ||
import { deleteScenarioShareTokenController } from "./delete-scenario-share-token-controller" | ||
import { db } from "../../../../db/db" | ||
|
||
jest.mock("../../../../db/db") | ||
|
||
|
||
const mockResponse = () => { | ||
const res: Partial<Response> = {} | ||
res.send = jest.fn().mockReturnValue(res) | ||
res.status = jest.fn().mockReturnValue(res) | ||
res.json = jest.fn().mockReturnValue(res) | ||
return res | ||
} | ||
|
||
describe("deleteScenarioShareTokenController", () => { | ||
it("should be able to delete only my token whe user role is operator", async () => { | ||
|
||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/scenario"), | ||
"deleteMyScenarioShareToken") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", shareTokenId: "my-share-token" }, | ||
user: { role: AllowedRoles.Operator, userId: uuidv4() }, | ||
}; | ||
(db.oneOrNone as any).mockReturnValueOnce({} ); // dummy token search response | ||
(db.none as any).mockReturnValueOnce() | ||
|
||
await deleteScenarioShareTokenController( | ||
request as unknown as IGetUserAuthInfoRequest, | ||
response as unknown as Response) | ||
|
||
expect(querySpy).toHaveBeenCalledTimes(1) | ||
expect(querySpy) | ||
.toHaveBeenLastCalledWith( | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
request.params.shareTokenId, | ||
request.user.userId) | ||
expect(response.send).toHaveBeenCalledTimes(1) | ||
}) | ||
it("should be able to delete any token whe user role is admin", async () => { | ||
|
||
const response = mockResponse() | ||
const querySpy = jest.spyOn(require("../../../queries/scenario"), | ||
"deleteScenarioShareToken") | ||
const request = { | ||
params: { projectName: "project", scenarioName: "scenario", shareTokenId: "my-share-token" }, | ||
user: { role: AllowedRoles.Admin, userId: uuidv4() }, | ||
}; | ||
(db.oneOrNone as any).mockReturnValueOnce({} ); // dummy token search response | ||
(db.none as any).mockReturnValueOnce() | ||
|
||
await deleteScenarioShareTokenController( | ||
request as unknown as IGetUserAuthInfoRequest, | ||
response as unknown as Response) | ||
|
||
expect(querySpy).toHaveBeenCalledTimes(1) | ||
expect(querySpy) | ||
.toHaveBeenLastCalledWith( | ||
request.params.projectName, | ||
request.params.scenarioName, | ||
request.params.shareTokenId | ||
) | ||
expect(response.send).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
}) |
34 changes: 34 additions & 0 deletions
34
src/server/controllers/scenario/share-token/delete-scenario-share-token-controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { IGetUserAuthInfoRequest } from "../../../middleware/request.model" | ||
import { Response } from "express" | ||
import { AllowedRoles } from "../../../middleware/authorization-middleware" | ||
import { db } from "../../../../db/db" | ||
import { StatusCode } from "../../../utils/status-code" | ||
import { | ||
deleteMyScenarioShareToken, | ||
deleteScenarioShareToken, | ||
findMyScenarioShareToken, | ||
findScenarioShareToken, | ||
} from "../../../queries/scenario" | ||
|
||
export const deleteScenarioShareTokenController = async (req: IGetUserAuthInfoRequest, res: Response) => { | ||
const { user } = req | ||
const { projectName, scenarioName, shareTokenId } = req.params | ||
if ([AllowedRoles.Operator].includes(user.role)) { | ||
const shareToken = await db.oneOrNone( | ||
findMyScenarioShareToken(projectName, scenarioName, shareTokenId, user.userId)) | ||
if (shareToken) { | ||
await db.none(deleteMyScenarioShareToken(projectName, scenarioName, shareTokenId, user.userId)) | ||
return res.status(StatusCode.Ok).send() | ||
} | ||
res.status(StatusCode.NotFound).send() | ||
} else { | ||
const shareToken = await db.oneOrNone( | ||
findScenarioShareToken(projectName, scenarioName, shareTokenId)) | ||
if (shareToken) { | ||
await db.none(deleteScenarioShareToken(projectName, scenarioName, shareTokenId)) | ||
return res.status(StatusCode.Ok).send() | ||
} | ||
res.status(StatusCode.NotFound).send() | ||
|
||
} | ||
} |
Oops, something went wrong.