-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
359 additions
and
0 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
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,174 @@ | ||
import { jest } from "@jest/globals"; | ||
import { | ||
createMetrics, | ||
incrementClicks, | ||
getMetricsByName, | ||
} from "../../controllers/metrics.js"; | ||
import MetricsModel from "../../models/Metrics.js"; | ||
import httpMocks from "node-mocks-http"; | ||
|
||
jest.mock("../../models/Metrics"); | ||
|
||
describe("createMetrics", () => { | ||
it("should successfully create a metrics record", async () => { | ||
const mockMetricsData = { | ||
clicks: 0, | ||
total_distribution: 50, | ||
}; | ||
// Mock the save function to return an object with the input data | ||
MetricsModel.prototype.save = jest.fn().mockImplementation(function () { | ||
return { ...mockMetricsData, _id: this._id }; | ||
}); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { total_distribution: 50 }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await createMetrics(req, res); | ||
|
||
const responseData = JSON.parse(res._getData()); | ||
|
||
expect(MetricsModel.prototype.save).toHaveBeenCalled(); | ||
expect(res.statusCode).toBe(200); | ||
expect(responseData.clicks).toBe(mockMetricsData.clicks); | ||
expect(responseData.total_distribution).toBe( | ||
mockMetricsData.total_distribution | ||
); | ||
expect(responseData).toHaveProperty("_id"); | ||
}); | ||
it("should return 500 on server errors", async () => { | ||
MetricsModel.prototype.save = jest.fn().mockImplementation(() => { | ||
throw new Error("Internal Server Error"); | ||
}); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { total_distribution: 50 }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await createMetrics(req, res); | ||
|
||
expect(res.statusCode).toBe(500); | ||
expect(res._getData()).toContain("Internal Server Error"); | ||
}); | ||
}); | ||
|
||
describe("incrementClicks", () => { | ||
it("should successfully increment the clicks of a metrics record", async () => { | ||
const mockMetricsData = { | ||
_id: "someMetricsId", | ||
metrics_name: "TestMetric", | ||
clicks: 1, | ||
}; | ||
MetricsModel.findOneAndUpdate = jest | ||
.fn() | ||
.mockResolvedValue(mockMetricsData); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { metricsName: "TestMetric" }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await incrementClicks(req, res); | ||
|
||
expect(MetricsModel.findOneAndUpdate).toHaveBeenCalledWith( | ||
{ metrics_name: "TestMetric" }, | ||
{ $inc: { clicks: 1 } }, | ||
{ new: true } | ||
); | ||
expect(res.statusCode).toBe(200); | ||
expect(JSON.parse(res._getData())).toEqual(mockMetricsData); | ||
}); | ||
it("should return 404 if the metrics record is not found", async () => { | ||
MetricsModel.findOneAndUpdate = jest.fn().mockResolvedValue(null); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { metricsName: "NonexistentMetric" }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await incrementClicks(req, res); | ||
|
||
expect(MetricsModel.findOneAndUpdate).toHaveBeenCalledWith( | ||
{ metrics_name: "NonexistentMetric" }, | ||
{ $inc: { clicks: 1 } }, | ||
{ new: true } | ||
); | ||
expect(res.statusCode).toBe(404); | ||
expect(res._getData()).toContain("Metrics record not found"); | ||
}); | ||
it("should handle server errors", async () => { | ||
MetricsModel.findOneAndUpdate.mockImplementationOnce(() => { | ||
throw new Error("Internal Server Error"); | ||
}); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { metricsName: "TestMetric" }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await incrementClicks(req, res); | ||
|
||
expect(res.statusCode).toBe(500); | ||
expect(res._getData()).toContain("Internal Server Error"); | ||
}); | ||
}); | ||
|
||
describe("getMetricsByName", () => { | ||
it("should successfully retrieve a metrics record by name", async () => { | ||
const mockMetricsData = { | ||
_id: "someMetricsId", | ||
metrics_name: "TestMetric", | ||
clicks: 10, | ||
total_distribution: 50, | ||
}; | ||
MetricsModel.findOne = jest.fn().mockResolvedValue(mockMetricsData); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { metricsName: "TestMetric" }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await getMetricsByName(req, res); | ||
|
||
expect(MetricsModel.findOne).toHaveBeenCalledWith({ | ||
metrics_name: "TestMetric", | ||
}); | ||
expect(res.statusCode).toBe(200); | ||
expect(JSON.parse(res._getData())).toEqual(mockMetricsData); | ||
}); | ||
|
||
it("should return 404 if the metrics record is not found", async () => { | ||
MetricsModel.findOne = jest.fn().mockResolvedValue(null); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { metricsName: "NonexistentMetric" }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await getMetricsByName(req, res); | ||
|
||
expect(MetricsModel.findOne).toHaveBeenCalledWith({ | ||
metrics_name: "NonexistentMetric", | ||
}); | ||
expect(res.statusCode).toBe(404); | ||
expect(res._getData()).toContain("Metrics record not found"); | ||
}); | ||
|
||
it("should handle server errors", async () => { | ||
MetricsModel.findOne.mockImplementationOnce(() => { | ||
throw new Error("Internal Server Error"); | ||
}); | ||
|
||
const req = httpMocks.createRequest({ | ||
body: { metricsName: "TestMetric" }, | ||
}); | ||
const res = httpMocks.createResponse(); | ||
|
||
await getMetricsByName(req, res); | ||
|
||
expect(res.statusCode).toBe(500); | ||
expect(res._getData()).toContain("Internal Server Error"); | ||
}); | ||
}); |
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,80 @@ | ||
import MetricsModel from "../models/Metrics.js"; | ||
import { | ||
handleServerError, | ||
handleSuccess, | ||
handleNotFound, | ||
} from "../utils/handlers.js"; | ||
|
||
/** | ||
* Creates a new metrics record. | ||
* | ||
* Initializes a new metrics record with default clicks (0) and a specified total_distribution from the request body. | ||
* | ||
* @param {Object} req - The request object containing the total_distribution value. | ||
* @param {Object} res - The response object to send back the created metrics data or an error message. | ||
*/ | ||
export const createMetrics = async (req, res) => { | ||
try { | ||
const totalDistribution = req.body.total_distribution || 50; // Default to 50 if not provided | ||
const metrics = new MetricsModel({ | ||
clicks: 0, | ||
total_distribution: totalDistribution, | ||
}); | ||
await metrics.save(); | ||
handleSuccess(res, metrics); | ||
} catch (err) { | ||
handleServerError(res, err); | ||
} | ||
}; | ||
|
||
/** | ||
* Increments the click counter of a metrics record. | ||
* | ||
* Accepts a metrics name from the request body and increments its clicks counter by 1. | ||
* | ||
* @param {Object} req - Request object containing the metricsName in the body. | ||
* @param {Object} res - Response object to send back the updated metrics data or an error message. | ||
*/ | ||
export const incrementClicks = async (req, res) => { | ||
try { | ||
const { metricsName } = req.body; | ||
const metrics = await MetricsModel.findOneAndUpdate( | ||
{ metrics_name: metricsName }, | ||
{ $inc: { clicks: 1 } }, | ||
{ new: true } | ||
); | ||
|
||
if (!metrics) { | ||
return handleNotFound(res, "Metrics record not found"); | ||
} | ||
|
||
handleSuccess(res, metrics); | ||
} catch (err) { | ||
handleServerError(res, err); | ||
} | ||
}; | ||
|
||
/** | ||
* Retrieves a metrics record by its name. | ||
* | ||
* Fetches a metrics record from the database using its name. | ||
* | ||
* @param {Object} req - Request object containing the metricsName in the body. | ||
* @param {Object} res - Response object to return the metrics data or an error message. | ||
*/ | ||
export const getMetricsByName = async (req, res) => { | ||
try { | ||
const { metricsName } = req.body; | ||
const metrics = await MetricsModel.findOne({ | ||
metrics_name: metricsName, | ||
}); | ||
|
||
if (!metrics) { | ||
return handleNotFound(res, "Metrics record not found"); | ||
} | ||
|
||
handleSuccess(res, metrics); | ||
} catch (err) { | ||
handleServerError(res, err); | ||
} | ||
}; |
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,35 @@ | ||
/** | ||
* Metrics Model Schema | ||
* | ||
* Defines the Mongoose schema for metrics records in the application. It includes fields for metrics name, | ||
* click count, and total distribution. The metrics name is unique for each record, ensuring no duplicates. | ||
* The schema is used to for A/B testing of the application. | ||
* | ||
* Schema Fields: | ||
* - metrics_name: Unique name identifier for the metrics record. | ||
* - clicks: Count of clicks, used for tracking user interactions. | ||
* - total_distribution: Represents the distribution value, defaulting to 50. | ||
* | ||
*/ | ||
|
||
import mongoose from "mongoose"; | ||
const { Schema } = mongoose; | ||
|
||
export const MetricsSchema = new Schema({ | ||
clicks: { | ||
type: Number, | ||
default: 0, | ||
}, | ||
total_distribution: { | ||
type: Number, | ||
default: 50, | ||
}, | ||
metrics_name: { | ||
type: String, | ||
required: true, | ||
unique: true, | ||
}, | ||
}); | ||
|
||
const MetricsModel = mongoose.model("Metrics", MetricsSchema); | ||
export default MetricsModel; |
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,29 @@ | ||
/** | ||
* Metrics Routes | ||
* | ||
* Express routes for operations related to metrics records. Includes routes for | ||
* creating, incrementing the click count of a metric, and retrieving metric data by name. | ||
* The request handling is delegated to the metrics controller. | ||
* | ||
* Routes: | ||
* - POST /create-metrics: Handles the creation of a new metrics record. | ||
* - POST /increment-clicks: Handles incrementing the click count of a metric. | ||
* - POST /get-metrics: Retrieves a metrics record by its name. | ||
*/ | ||
|
||
import express from "express"; | ||
import { | ||
createMetrics, | ||
incrementClicks, | ||
getMetricsByName, | ||
} from "../controllers/metrics.js"; | ||
|
||
const router = express.Router(); | ||
|
||
router.post("/create-metrics", createMetrics); | ||
|
||
router.post("/increment-clicks", incrementClicks); | ||
|
||
router.post("/get-metrics", getMetricsByName); | ||
|
||
export default router; |