Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/yale-swe/f23-here
Browse files Browse the repository at this point in the history
  • Loading branch information
annazhang2001 committed Dec 2, 2023
2 parents 53a8601 + 7a93ef7 commit 6569ed9
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 0 deletions.
41 changes: 41 additions & 0 deletions API-Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,45 @@ Add a reply to a message.
"message_id": "653ea4a35b858b2542ea4f13",
"content": "this is a test reply to a test message"
}
```

## Metrics Endpoints

### Create Metrics

Creates a new metrics record.

- **Endpoint:** `POST /metrics/create-metrics`
- **Body:**

```json
{
"total_distribution": 50
}
```

### Increment Clicks

Increments the click count of a specified metrics record by 1.

- **Endpoint:** `POST /metrics/increment-clicks`
- **Body:**

```json
{
"metricsName": "DailyUserVisits"
}
```

### Get Metrics by Name

Retrieves a metrics record by its name.

- **Endpoint:** `POST /metrics/get-metrics`
- **Body:**

```json
{
"metricsName": "DailyUserVisits"
}
```
174 changes: 174 additions & 0 deletions server/__tests__/controllers/metricsTest.js
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,
metrics_name: "TestMetric",
};
MetricsModel.prototype.save = jest.fn().mockImplementation(function () {
return { ...mockMetricsData, _id: this._id };
});

const req = httpMocks.createRequest({
body: { total_distribution: 50, metrics_name: "TestMetric" },
});
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).toMatchObject(mockMetricsData);
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, metrics_name: "TestMetric" },
});
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({
message: "Metrics incremented sucessfully",
});
});
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");
});
});
97 changes: 97 additions & 0 deletions server/controllers/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Metrics Controller
*
* This file serves as the controller for metrics-related operations in the API.
* It includes functionalities for managing metrics records such as creating new records,
* incrementing click counts, and retrieving metrics data by name.
*
* Dependencies:
* - MetricsModel: The Mongoose model used for metrics data interactions with the MongoDB database.
* - Handlers: Utility functions for handling various HTTP response scenarios, such as server errors,
* successful responses, and resource not found errors.
*/

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 { total_distribution = 50, metrics_name } = req.body;

const metrics = new MetricsModel({
clicks: 0,
total_distribution,
metrics_name,
});

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 { metrics_name } = req.body;
console.log(metrics_name);
const metrics = await MetricsModel.findOneAndUpdate(
{ metrics_name: metrics_name },
{ $inc: { clicks: 1 } },
{ new: true }
);

if (!metrics) {
return handleNotFound(res, "Metrics record not found");
}

handleSuccess(res, { message: "Metrics incremented sucessfully" });
} 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 { metrics_name } = req.body;
const metrics = await MetricsModel.findOne({
metrics_name: metrics_name,
});

if (!metrics) {
return handleNotFound(res, "Metrics record not found");
}

handleSuccess(res, metrics);
} catch (err) {
handleServerError(res, err);
}
};
35 changes: 35 additions & 0 deletions server/models/Metrics.js
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;
29 changes: 29 additions & 0 deletions server/routes/metrics.js
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;

0 comments on commit 6569ed9

Please sign in to comment.