Skip to content

Commit

Permalink
Feature: scenario trends sharing (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeknovy authored Nov 10, 2023
1 parent 98241b0 commit 9b89c50
Show file tree
Hide file tree
Showing 25 changed files with 974 additions and 312 deletions.
33 changes: 33 additions & 0 deletions migrations/1698218100284_scenario-share-token.js
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,
},
})
}
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)
})

})
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import { StatusCode } from "../../../utils/status-code"
export const deleteItemShareTokenController = async (req: IGetUserAuthInfoRequest, res: Response) => {
const { user } = req
const { projectName, scenarioName, itemId, tokenId } = req.params
if ([AllowedRoles.Readonly, AllowedRoles.Operator].includes(user.role)) {
if ([AllowedRoles.Operator].includes(user.role)) {
await db.none(deleteMyShareToken(projectName, scenarioName, itemId, tokenId, user.userId))
res.status(StatusCode.Ok).send()

} else {
await db.none(deleteShareToken(projectName, scenarioName, itemId, tokenId))
res.status(StatusCode.Ok).send()
}

}
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)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import { selectOnlyMyShareTokens, selectShareTokens } from "../../../queries/ite
import { StatusCode } from "../../../utils/status-code"

export const getItemLinksController = async (req: IGetUserAuthInfoRequest, res: Response) => {
const { role, userId } = req.user
const { projectName, scenarioName, itemId } = req.params
if ([AllowedRoles.Readonly, AllowedRoles.Operator].includes(role)) {
const myApiKeys = await db.manyOrNone(selectOnlyMyShareTokens(projectName, scenarioName, itemId, userId))
return res.send(StatusCode.Ok).json(myApiKeys)
}
const { role, userId } = req.user
const { projectName, scenarioName, itemId } = req.params
if ([AllowedRoles.Operator].includes(role)) {
const myApiKeys = await db.manyOrNone(selectOnlyMyShareTokens(projectName, scenarioName, itemId, userId))
return res.send(StatusCode.Ok).json(myApiKeys)
}
const shareTokens = await db.manyOrNone(selectShareTokens(projectName, scenarioName, itemId))
res.status(StatusCode.Ok).json(shareTokens)

}
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 })
})
})
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 })
}
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)
})

})
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()

}
}
Loading

0 comments on commit 9b89c50

Please sign in to comment.