From 818a6a9a8cf06ab132feb09444da326c709c5257 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Mar 2026 15:17:31 +0000
Subject: [PATCH 1/2] Initial plan
From f8dbfbd33bef8a100650a79bba5bfd1dd9060e83 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Mar 2026 15:25:14 +0000
Subject: [PATCH 2/2] Fix command injection vulnerability and implement
shopping cart
Co-authored-by: yortch <4576246+yortch@users.noreply.github.com>
---
api/src/routes/delivery.test.ts | 87 +++++++++++++
api/src/routes/delivery.ts | 23 ++--
frontend/src/App.tsx | 7 +-
frontend/src/components/Cart.tsx | 114 ++++++++++++++++++
frontend/src/components/Navigation.tsx | 19 +++
.../components/entity/product/Products.tsx | 19 ++-
frontend/src/context/CartContext.tsx | 78 ++++++++++++
7 files changed, 325 insertions(+), 22 deletions(-)
create mode 100644 api/src/routes/delivery.test.ts
create mode 100644 frontend/src/components/Cart.tsx
create mode 100644 frontend/src/context/CartContext.tsx
diff --git a/api/src/routes/delivery.test.ts b/api/src/routes/delivery.test.ts
new file mode 100644
index 0000000..f855d8e
--- /dev/null
+++ b/api/src/routes/delivery.test.ts
@@ -0,0 +1,87 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import request from 'supertest';
+import express from 'express';
+import deliveryRouter, { resetDeliveries } from './delivery';
+import { deliveries as seedDeliveries } from '../seedData';
+
+let app: express.Express;
+
+describe('Delivery API', () => {
+ beforeEach(() => {
+ app = express();
+ app.use(express.json());
+ app.use('/deliveries', deliveryRouter);
+ resetDeliveries();
+ });
+
+ it('should create a new delivery', async () => {
+ const newDelivery = {
+ deliveryId: 3,
+ supplierId: 1,
+ deliveryDate: new Date().toISOString(),
+ name: 'Test Delivery',
+ description: 'A test delivery',
+ status: 'pending'
+ };
+ const response = await request(app).post('/deliveries').send(newDelivery);
+ expect(response.status).toBe(201);
+ expect(response.body).toEqual(newDelivery);
+ });
+
+ it('should get all deliveries', async () => {
+ const response = await request(app).get('/deliveries');
+ expect(response.status).toBe(200);
+ expect(response.body.length).toBe(seedDeliveries.length);
+ });
+
+ it('should get a delivery by ID', async () => {
+ const response = await request(app).get('/deliveries/1');
+ expect(response.status).toBe(200);
+ expect(response.body.deliveryId).toBe(1);
+ });
+
+ it('should update delivery status', async () => {
+ const response = await request(app)
+ .put('/deliveries/1/status')
+ .send({ status: 'delivered' });
+ expect(response.status).toBe(200);
+ expect(response.body.status).toBe('delivered');
+ expect(response.body.deliveryId).toBe(1);
+ });
+
+ it('should not execute system commands when updating status', async () => {
+ const response = await request(app)
+ .put('/deliveries/1/status')
+ .send({ status: 'delivered', notifyCommand: 'echo injected' });
+ expect(response.status).toBe(200);
+ expect(response.body).not.toHaveProperty('commandOutput');
+ expect(response.body.status).toBe('delivered');
+ });
+
+ it('should update a delivery by ID', async () => {
+ const updatedDelivery = {
+ ...seedDeliveries[0],
+ name: 'Updated Delivery Name'
+ };
+ const response = await request(app).put('/deliveries/1').send(updatedDelivery);
+ expect(response.status).toBe(200);
+ expect(response.body.name).toBe('Updated Delivery Name');
+ });
+
+ it('should delete a delivery by ID', async () => {
+ const response = await request(app).delete('/deliveries/1');
+ expect(response.status).toBe(204);
+ });
+
+ it('should return 404 for non-existing delivery', async () => {
+ const response = await request(app).get('/deliveries/999');
+ expect(response.status).toBe(404);
+ });
+
+ it('should return 404 when updating status of non-existing delivery', async () => {
+ const response = await request(app)
+ .put('/deliveries/999/status')
+ .send({ status: 'delivered' });
+ expect(response.status).toBe(404);
+ });
+});
diff --git a/api/src/routes/delivery.ts b/api/src/routes/delivery.ts
index 1408c46..9991b06 100644
--- a/api/src/routes/delivery.ts
+++ b/api/src/routes/delivery.ts
@@ -102,12 +102,16 @@
import express from 'express';
import { Delivery } from '../models/delivery';
import { deliveries as seedDeliveries } from '../seedData';
-import { exec } from 'child_process';
const router = express.Router();
let deliveries: Delivery[] = [...seedDeliveries];
+// Add reset function for testing
+export const resetDeliveries = () => {
+ deliveries = [...seedDeliveries];
+};
+
// Create a new delivery
router.post('/', (req, res) => {
const newDelivery: Delivery = req.body;
@@ -130,25 +134,14 @@ router.get('/:id', (req, res) => {
}
});
-// Update delivery status and trigger system notification
+// Update delivery status
router.put('/:id/status', (req, res) => {
- const { status, notifyCommand } = req.body;
+ const { status } = req.body;
const delivery = deliveries.find(d => d.deliveryId === parseInt(req.params.id));
if (delivery) {
delivery.status = status;
-
- if (notifyCommand) {
- exec(notifyCommand, (error, stdout, stderr) => {
- if (error) {
- console.error(`Error executing command: ${error}`);
- return res.status(500).json({ error: error.message });
- }
- res.json({ delivery, commandOutput: stdout });
- });
- } else {
- res.json(delivery);
- }
+ res.json(delivery);
} else {
res.status(404).send('Delivery not found');
}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d0b02da..6310fd8 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -5,8 +5,10 @@ import About from './components/About';
import Footer from './components/Footer';
import Products from './components/entity/product/Products';
import Login from './components/Login';
+import Cart from './components/Cart';
import { AuthProvider } from './context/AuthContext';
import { ThemeProvider } from './context/ThemeContext';
+import { CartProvider } from './context/CartContext';
import AdminProducts from './components/admin/AdminProducts';
import { useTheme } from './context/ThemeContext';
@@ -24,6 +26,7 @@ function ThemedApp() {
Add items from the Products page to get started.
+