Skip to content
Merged
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
Binary file not shown.
93 changes: 93 additions & 0 deletions .github/workflows/cipipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: CI Test Pipeline

on:
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
continue-on-error: false

env:
PYTHONPATH: ${{ github.workspace }}
NEXT_PUBLIC_MOCK_GEMINI: true
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}

steps:
- name: 🧾 Checkout code
uses: actions/checkout@v3

- name: ⚙️ Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- name: ⚙️ Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: ♻️ Cache Python dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: ${{ runner.os }}-pip-

- name: ♻️ Cache Node modules
uses: actions/cache@v3
with:
path: ./fitcheck/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('fitcheck/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov playwright
playwright install

- name: Install frontend dependencies
working-directory: ./fitcheck
run: npm install

- name: Build Next.js app
working-directory: ./fitcheck
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
NEXT_PUBLIC_MOCK_GEMINI: true
run: npm run build

- name: Start Next.js server in background
working-directory: ./fitcheck
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
NEXT_PUBLIC_MOCK_GEMINI: true
run: |
nohup npm start > ../nextjs.log 2>&1 &
Copy link

Copilot AI May 15, 2025

Choose a reason for hiding this comment

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

[nitpick] Using 'nohup' for server startup might complicate process management; consider utilizing a dedicated process manager or GitHub Actions background job control instead.

Copilot uses AI. Check for mistakes.

- name: Wait for server to be ready
run: |
for i in {1..10}; do
curl --fail http://localhost:3000/login && break || sleep 3
done || (echo "Server failed to start or /login not reachable" && cat fitcheck/../nextjs.log && exit 1)

- name: Run Python tests with coverage
run: pytest --cov=./ --cov-report=html

- name: Run Playwright E2E test (Login + Dashboard)
run: python tests/test_login_e2e.py

- name: Run Playwright E2E test (Extension UI)
run: xvfb-run -a python tests/test_popup_ui.py

- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: htmlcov
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ node_modules/
babel.config.js
.env.local

__pycache__/
*.py[cod]
88 changes: 46 additions & 42 deletions fitcheck/pages/api/user_rec/getRec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import supabase from '../../../utils/supabase/config';
import recommendationService from '../../../utils/supabase/recommendationService';



export default async function handler(req, res) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Only GET supported' });
Expand All @@ -14,53 +12,59 @@ export default async function handler(req, res) {
return res.status(401).json({ error: 'Missing or invalid Authorization header' });
}

const token = authHeader.split('Bearer ')[1];
const { data: userData, error: userError } = await supabase.auth.getUser(token);
try {
const token = authHeader.split('Bearer ')[1];
const { data: userData, error: userError } = await supabase.auth.getUser(token);

if (userError || !userData?.user) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
if (userError || !userData?.user) {
console.error('Auth error:', userError?.message || 'No user data');
return res.status(401).json({ error: 'Invalid or expired token' });
}

const email = userData.user.email;
const email = userData.user.email;

// Get user ID from 'users' table
const { data: userRow, error: userLookupError } = await supabase
.from('users')
.select('userid')
.eq('email', email)
.single();
const { data: userRow, error: userLookupError } = await supabase
.from('users')
.select('userid')
.eq('email', email)
.single();

if (userLookupError || !userRow) {
return res.status(404).json({ error: 'User not found' });
}
if (userLookupError || !userRow) {
console.error('User lookup failed:', userLookupError?.message || 'User not found');
return res.status(404).json({ error: 'User not found' });
}

// Get sentiment reviews
const { data: sentimentData, error: sentimentError } = await supabase
.from('sentiment')
.select('*')
.eq('userid', userRow.userid)
.order('created_at', { ascending: false });
const { data: sentimentData, error: sentimentError } = await supabase
.from('sentiment')
.select('*')
.eq('userid', userRow.userid)
.order('created_at', { ascending: false });

if (sentimentError) {
return res.status(500).json({ error: 'Failed to fetch sentiment data' });
}
if (sentimentError) {
console.error('Sentiment fetch error:', sentimentError.message);
return res.status(500).json({ error: 'Failed to fetch sentiment data' });
}

const reviews = sentimentData.map(item => ({
productName: item.product_name,
rating: item.star_rating,
likedFeatures: item.sentiment_label === 'positive' ? 'style, comfort' : '',
dislikedFeatures: item.sentiment_label === 'negative' ? 'fit, quality' : '',
comment: item.review_text
}));

// Transform reviews into Gemini-compatible format
const reviews = sentimentData.map(item => ({
productName: item.product_name,
rating: item.star_rating,
likedFeatures: item.sentiment_label === 'positive' ? 'style, comfort' : '',
dislikedFeatures: item.sentiment_label === 'negative' ? 'fit, quality' : '',
comment: item.review_text
}));
const userPayload = {
reviews: reviews.slice(0, 3),
stylePreferences: {},
purchaseHistory: [],
budget: { weeklyAmount: 100, spentAmount: 0 }
};

const userPayload = {
reviews: reviews.slice(0, 3), // ✅ limit to 3 to avoid token limit
stylePreferences: {},
purchaseHistory: [],
budget: { weeklyAmount: 100, spentAmount: 0 }
};
const recommendations = await recommendationService.generateRecommendations(userPayload);
return res.status(200).json({ recommendations });

const recommendations = await recommendationService.generateRecommendations(userPayload);
return res.status(200).json({ recommendations });
} catch (err) {
console.error('Unexpected server error in getReviews:', err);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
Binary file not shown.
40 changes: 0 additions & 40 deletions fitcheck/tests/test_login.py

This file was deleted.

12 changes: 12 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fastapi
requests
pytest
pytest-cov
pytest-asyncio
pytest-tornasync
pytest-trio
pytest-twisted
playwright
python-dotenv
twisted
git+https://github.com/supabase-community/supabase-py.git
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added tests/debug_login_flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions tests/login_integration_test.py
Copy link
Collaborator

Choose a reason for hiding this comment

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

These integration tests are thorough and test different edge cases!! Good work

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import pytest
import requests

BASE_URL = "http://localhost:3000/api/auth"

TEST_EMAIL = "gafocoy789@dmener.com"
TEST_PASSWORD = "password123"
TEST_USER = {
"email": TEST_EMAIL,
"password": TEST_PASSWORD,
"first_name": "Test",
"last_name": "User",
"gender": "Other"
}

@pytest.fixture(scope="module")
def cleanup_user():
yield
print("Cleanup after tests")

def test_successful_signup_and_login(cleanup_user):
try:
response = requests.post(f"{BASE_URL}/signup", json=TEST_USER)
print("Signup response:", response.status_code, response.json())
if response.status_code not in (200, 400):
pytest.skip("Signup failed unexpectedly")

Comment on lines +21 to +27
Copy link

Copilot AI May 15, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider splitting the combined test for successful signup and login into two separate tests to clearly delineate expected behaviors for each scenario.

Suggested change
def test_successful_signup_and_login(cleanup_user):
try:
response = requests.post(f"{BASE_URL}/signup", json=TEST_USER)
print("Signup response:", response.status_code, response.json())
if response.status_code not in (200, 400):
pytest.skip("Signup failed unexpectedly")
def test_successful_signup(cleanup_user):
try:
response = requests.post(f"{BASE_URL}/signup", json=TEST_USER)
print("Signup response:", response.status_code, response.json())
if response.status_code not in (200, 400):
pytest.skip("Signup failed unexpectedly")
assert response.status_code == 200
assert response.json()["message"] in ["Please verify your email"]
except Exception as e:
pytest.skip(f"Test skipped due to exception: {e}")
def test_successful_login(cleanup_user):
try:

Copilot uses AI. Check for mistakes.
login_payload = {"email": TEST_EMAIL, "password": TEST_PASSWORD}
response = requests.post(f"{BASE_URL}/login", json=login_payload)
print("Login response:", response.status_code, response.json())

assert response.status_code in (200, 400)
if response.status_code == 200:
assert response.json()["message"] in ["Login Successful", "Please Verify Your Email"]
else:
assert response.json()["error"] == "Invalid login credentials"

except Exception as e:
pytest.skip(f"Test skipped due to exception: {e}")

def test_signup_duplicate_email():
try:
response = requests.post(f"{BASE_URL}/signup", json=TEST_USER)
print("Duplicate signup response:", response.status_code, response.json())
if response.status_code != 200:
pytest.skip("Duplicate signup failed unexpectedly")

assert "message" in response.json()
assert response.json()["message"] in [
"Please verify your email",
"User already registered. Please verify your email"
]
except Exception as e:
pytest.skip(f"Test skipped due to exception: {e}")

def test_login_invalid_credentials():
try:
payload = {"email": "wrong@example.com", "password": "wrongpass"}
response = requests.post(f"{BASE_URL}/login", json=payload)
print("Invalid login response:", response.status_code, response.json())

assert response.status_code == 400
assert response.json()["error"] == "Invalid login credentials"
except Exception as e:
pytest.skip(f"Test skipped due to exception: {e}")
45 changes: 45 additions & 0 deletions tests/test_content_extract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { JSDOM } = require("jsdom");

// 📦 Realistic Amazon-like mock HTML
const html = `
<div class="order-card__list">
<div class="a-row">Order # 123-4567890-1234567</div>
<div>
<span class="a-size-base a-color-secondary aok-break-word">Jan 1, 2024</span>
<span class="a-size-base a-color-secondary aok-break-word">$19.99</span>
</div>
<a class="a-link-normal" href="/dp/ABC123">Product 1</a>
</div>
`;

// 🧠 Create JSDOM first
const dom = new JSDOM(html);
global.document = dom.window.document;
global.window = dom.window;
global.Node = dom.window.Node;

// ✅ Shim innerText after DOM is initialized
Object.defineProperty(global.window.HTMLElement.prototype, 'innerText', {
get() {
return this.textContent;
}
});

// 🛠️ Mock Chrome extension API
global.chrome = {
runtime: {
onMessage: {
addListener: () => {},
}
}
};

// ✅ Now import the logic AFTER mocks are in place
const { extractOrders } = require("../amazon-order-csv/content.js");

// 🧪 Run test
extractOrders().then(result => {
console.log("✅ Extracted Orders:\n", JSON.stringify(result, null, 2));
}).catch(err => {
console.error("❌ Error during extraction:", err);
});
Loading
Loading