Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to checking OS X artifact into the repository.

Binary file not shown.
103 changes: 103 additions & 0 deletions Chrome Extension/__tests__/purchase-detection.integration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Integration tests for purchase detection and saving
describe('Purchase Detection and Storage Integration', () => {
beforeEach(() => {

document.body.innerHTML = `
<div class="product-title">Test Product</div>
<div class="price">$29.99</div>
`;


chrome.runtime.sendMessage.mockImplementation((message, callback) => {
if (message.type === 'PURCHASE_DETECTED') {

savePurchaseData(message.data);
}
if (callback) callback({ success: true });
return true;
});


chrome.storage.local.get.mockImplementation((keys, callback) => {
if (keys.includes('userToken')) {
callback({ userToken: 'test-token-123' });
}
});


global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ success: true, id: 'saved-purchase-123' })
})
);
});

test('should detect purchase and send data to background script', () => {

Object.defineProperty(window, 'location', {
value: { href: 'https://example.com/confirmation' }
});


detectPurchase();


expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({
type: 'PURCHASE_DETECTED',
data: expect.objectContaining({
item: 'Test Product',
price: '$29.99',
url: 'https://example.com/confirmation'
})
});
});

test('should store purchase data when user is logged in', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes this an integration tests?

const purchaseData = {
item: 'Test Product',
price: '$29.99',
url: 'https://example.com/product',
timestamp: new Date().toISOString()
};


await savePurchaseData(purchaseData);


expect(chrome.storage.local.get).toHaveBeenCalledWith(['userToken'], expect.any(Function));


expect(fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Authorization': 'Bearer test-token-123'
}),
body: expect.stringContaining('Test Product')
})
);
});

test('should prompt for login when token is missing', async () => {

chrome.storage.local.get.mockImplementationOnce((keys, callback) => {
callback({});
});


const promptUserLogin = jest.fn();

const purchaseData = {
item: 'Test Product',
price: '$29.99'
};

await savePurchaseData(purchaseData);

// Verify login was prompted
expect(promptUserLogin).toHaveBeenCalled();
// Verify API was not called
expect(fetch).not.toHaveBeenCalled();
});
});
73 changes: 73 additions & 0 deletions Chrome Extension/__tests__/review-management.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Unit tests for review functionality in background.js
describe('Review Management Unit Tests', () => {
beforeEach(() => {
chrome.storage.local.get.mockImplementation((keys, callback) => {
callback({ pendingReviews: [
{
purchaseId: 'test123',
product: 'Test Product',
reviewDue: new Date().toISOString(),
url: 'https://example.com/product'
}
]});
});

chrome.storage.local.set.mockImplementation((obj, callback) => {
if (callback) callback();
});

global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ success: true })
})
);
});

test('should schedule a review reminder with correct timing', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes this a unit test?

const purchaseData = {
id: 'purchase123',
item: 'Test Item',
timestamp: new Date().toISOString(),
url: 'https://example.com/item'
};

scheduleReviewReminder(purchaseData);

expect(chrome.storage.local.get).toHaveBeenCalledWith(['pendingReviews'], expect.any(Function));
expect(chrome.storage.local.set).toHaveBeenCalled();

const setArg = chrome.storage.local.set.mock.calls[0][0];
expect(setArg.pendingReviews.length).toBe(2); // Previous + new one
expect(setArg.pendingReviews[1].purchaseId).toBe('purchase123');

const reviewDate = new Date(setArg.pendingReviews[1].reviewDue);
const purchaseDate = new Date(purchaseData.timestamp);
const diffDays = Math.round((reviewDate - purchaseDate) / (1000 * 60 * 60 * 24));
expect(diffDays).toBe(7);
});

test('should remove review from pending list after submission', () => {
const purchaseId = 'test123';

removeFromPendingReviews(purchaseId);

expect(chrome.storage.local.get).toHaveBeenCalledWith(['pendingReviews'], expect.any(Function));
expect(chrome.storage.local.set).toHaveBeenCalled();

const setArg = chrome.storage.local.set.mock.calls[0][0];
expect(setArg.pendingReviews.length).toBe(0);
});

test('should handle empty pending reviews gracefully', () => {
chrome.storage.local.get.mockImplementationOnce((keys, callback) => {
callback({ pendingReviews: [] });
});

const purchaseId = 'nonexistent';
removeFromPendingReviews(purchaseId);

expect(chrome.storage.local.set).toHaveBeenCalled();
const setArg = chrome.storage.local.set.mock.calls[0][0];
expect(setArg.pendingReviews).toEqual([]);
});
});
203 changes: 203 additions & 0 deletions Chrome Extension/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@

chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed');

checkUserLoggedIn();
});


function checkUserLoggedIn() {
chrome.storage.local.get(['userToken'], function(result) {
/* so here we need to add the endpoint to ensure that the token is still valid, ensuring that the user is
properly logged in, if they arent we would go to the front end page and ensure that they are logged in

this would be done in tandem to @anas and @nirath as we need to check the database and the front end signUp page
*/
});
}


chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'PURCHASE_DETECTED') {
/* Here we would have the endpoint to take the purchase data and send it to the back end
we have to decide how we store the purchases properly still
ideally we just check the userid and check the date and time and use those to save the purchases
so that when we are returning purchases and budget, its done through set dates

ultimately makes it easier for us to filter*/

savePurchaseData(message.data);
} else if (message.type === 'LOGIN_REQUIRED') {
/* is user is not logged in (line 9), we would then route them to the log in page
we ensure the log in, once logged in

if new user do survey

if not new user we would just jump into working fully */

promptUserLogin();
}

return true; //keep the message channel open for async response
});

function savePurchaseData(data) {
chrome.storage.local.get(['userToken'], function(result) {
if (!result.userToken) {
promptUserLogin();
return;
}
/* endpoint to save the purchase data, header should include auth token,
data should show all purchase details*/

});
}

function promptUserLogin() {

/* The endpoint to handle auth flow
this should reroute to the login page that @nirath is working on

once successful log in is done, we need that to communicate to the extension so
that it can start working
it would communicate the auth token for that user*/

chrome.tabs.create({ url: 'insert the url here' });
}


chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
if (message.type === 'AUTH_TOKEN') {
/* endpoint to store the auth token
this should save it to the chrome.storage.local

this token will be used for all subsequent api calls that are being done*/

chrome.storage.local.set({ userToken: message.token });
sendResponse({ success: true });
}

return true;
});

function scheduleReviewReminder(purchaseData) {
/* set up a reminder in the database for specific purchases,
we then use that reminder to prompt the user to make a review of a purchase lets say after like 10 days from buying it */

const purchaseDate = new Date(purchaseData.timestamp);
const reviewDate = new Date(purchaseDate);
reviewDate.setDate(reviewDate.getDate() + 7); // One week later

//review reminders stored locally
chrome.storage.local.get(['pendingReviews'], function(result) {
const pendingReviews = result.pendingReviews || [];
pendingReviews.push({
purchaseId: purchaseData.id,
product: purchaseData.item,
reviewDue: reviewDate.toISOString(),
url: purchaseData.url
});

chrome.storage.local.set({ pendingReviews });
});
}

function saveProductReview(reviewData) {
chrome.storage.local.get(['userToken'], function(result) {
if (!result.userToken) {
promptUserLogin();
return;
}

/* create an endpoint to save the review data and send it to the ML model for better fine tuning reviews*/

fetch('https://yourapi.com/api/reviews/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${result.userToken}`
},
body: JSON.stringify(reviewData)
})
.then(response => response.json())
.then(data => {
//update the prending review list
removeFromPendingReviews(reviewData.purchaseId);

//updates the user with the confirmation
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'Review Submitted',
message: 'Thank you for your feedback! It helps improve recommendations for future purchases!.'
});
})
.catch(error => {
console.error('Error saving review:', error);
});
});
}

//checking for pending due reviews
function checkPendingReviews() {
chrome.storage.local.get(['pendingReviews'], function(result) {
const pendingReviews = result.pendingReviews || [];
const now = new Date();

pendingReviews.forEach(review => {
const reviewDue = new Date(review.reviewDue);
if (reviewDue <= now) {
promptForReview(review);
}
});
});
}

function promptForReview(reviewItem) {
/* create the popup for users to leave a review, can honestly look like

item price
link 1-5 stars

Typed message review (if we go that route)*/

chrome.windows.create({
url: `review.html?purchaseId=${reviewItem.purchaseId}&product=${encodeURIComponent(reviewItem.product)}`,
type: 'popup',
width: 400,
height: 500
});
}

//removes review from pending reviews, once a submission has been made
function removeFromPendingReviews(purchaseId) {
chrome.storage.local.get(['pendingReviews'], function(result) {
const pendingReviews = result.pendingReviews || [];
const updatedReviews = pendingReviews.filter(review => review.purchaseId !== purchaseId);
chrome.storage.local.set({ pendingReviews: updatedReviews });
});
}

chrome.alarms.create('checkReviews', { periodInMinutes: 1440 }); // Once per day


chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'checkReviews') {
checkPendingReviews();
}
});


chrome.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => {
if (buttonIndex === 0) { // "See Details" button
chrome.storage.local.get(['latestRecommendation'], function(result) {
if (result.latestRecommendation) {
//open the recommendation details page
chrome.tabs.create({
url: `recommendation.html?data=${encodeURIComponent(JSON.stringify(result.latestRecommendation))}`
});
}
});
}

});
Loading