mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
asset viewer now viewing on tabs
Tabs is better for this yay Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
const { Course } = require('../../api/models/courses/courses.mdl');
|
||||
const Unit = require('../../api/models/courses/units.mdl');
|
||||
const Lesson = require('../../api/models/courses/lessons.mdl');
|
||||
const { TaskList, Task } = require('../../api/models/task/task.mdl');
|
||||
const Advertisement = require('../../api/models/advertisements/advertisements.mdl');
|
||||
const TierPlan = require('../../api/models/tiers/tier_plans.mdl');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
const QA_PREFIX = '[QA-TEST';
|
||||
|
||||
const TARGETS = [
|
||||
{ model: Task, column: 'name' },
|
||||
{ model: TaskList, column: 'name' },
|
||||
{ model: Advertisement, column: 'headline' },
|
||||
{ model: Unit, column: 'title' },
|
||||
{ model: Lesson, column: 'title' },
|
||||
{ model: Course, column: 'title' },
|
||||
{ model: TierPlan, column: 'label' },
|
||||
];
|
||||
|
||||
/** Hard-deletes every QA-TEST-prefixed fixture row across all modules this suite touches. */
|
||||
async function cleanupQaTestFixtures() {
|
||||
const removed = {};
|
||||
for (const { model, column } of TARGETS) {
|
||||
const count = await model.destroy({
|
||||
where: { [column]: { [Op.like]: `${QA_PREFIX}%` } },
|
||||
force: true, // hard delete, not just paranoid soft-delete — mirrors the manual "archive then permanently delete" cleanup flow
|
||||
});
|
||||
if (count) removed[model.name] = count;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
module.exports = { cleanupQaTestFixtures, QA_PREFIX };
|
||||
@@ -0,0 +1,23 @@
|
||||
// Reuses the app's own Sequelize connection/config instead of duplicating
|
||||
// DB_HOST/DB_PORT/... wiring — env.js pre-loads apps/api/.env so db.config.js
|
||||
// picks up the real dev credentials when it runs its own (no-op, already-set)
|
||||
// dotenv.config() call.
|
||||
require('./env');
|
||||
const sequelize = require('../../api/config/db.config');
|
||||
const Users = require('../../api/models/users/users.mdl');
|
||||
|
||||
async function getUserIdByEmail(email) {
|
||||
const user = await Users.findOne({ where: { email }, attributes: ['user_id'] });
|
||||
if (!user) throw new Error(`No user found for email ${email} — check the dev DB seed / helpers/env.js USERS config.`);
|
||||
return String(user.user_id);
|
||||
}
|
||||
|
||||
async function getPendingOtp(user_id) {
|
||||
const rows = await sequelize.query(
|
||||
`SELECT otp_code FROM users WHERE user_id = :uid`,
|
||||
{ replacements: { uid: user_id }, type: sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
return rows[0]?.otp_code ?? null;
|
||||
}
|
||||
|
||||
module.exports = { sequelize, getUserIdByEmail, getPendingOtp };
|
||||
@@ -0,0 +1,33 @@
|
||||
// Loads real dev secrets from apps/api/.env (gitignored — never committed).
|
||||
// Falls back to apps/api/.env-development (git-tracked shared dev creds) for
|
||||
// anything .env doesn't override, matching how apps/api itself resolves config.
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../api/.env') });
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../api/.env-development') });
|
||||
|
||||
const APP_URL = process.env.E2E_APP_URL || 'http://localhost:5173';
|
||||
const API_URL = process.env.E2E_API_URL || 'http://localhost:3024/api';
|
||||
|
||||
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;
|
||||
if (!JWT_REFRESH_SECRET || JWT_REFRESH_SECRET.startsWith('CHANGE_ME')) {
|
||||
throw new Error('JWT_REFRESH_SECRET missing/placeholder — check apps/api/.env has real dev secrets.');
|
||||
}
|
||||
|
||||
// Known test accounts. user_id is intentionally NOT hardcoded here — these
|
||||
// accounts get recreated over time (see chibistar/automation/starr/config.cjs
|
||||
// history) which silently breaks hardcoded ids. helpers/session.js looks the
|
||||
// id up by email at call time instead.
|
||||
const USERS = {
|
||||
admin: {
|
||||
email: process.env.E2E_ADMIN_EMAIL || 'k80308392@gmail.com',
|
||||
password: process.env.E2E_ADMIN_PASSWORD || 'Test123@',
|
||||
acc_type: 'admin',
|
||||
},
|
||||
seedClient: {
|
||||
// Synthetic, no real owner — safe default for read-only/client-facing checks.
|
||||
email: process.env.E2E_SEED_CLIENT_EMAIL || 'veronica.castro@example.com',
|
||||
acc_type: 'user',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { APP_URL, API_URL, JWT_REFRESH_SECRET, USERS };
|
||||
@@ -0,0 +1,55 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const { APP_URL, JWT_REFRESH_SECRET, USERS } = require('./env');
|
||||
const { sequelize, getUserIdByEmail, getPendingOtp } = require('./db');
|
||||
|
||||
/** Build the cookie jar for context.addCookies() from a refresh token. */
|
||||
function buildCookies(refreshToken, csrfToken = 'e2e-csrf') {
|
||||
return [
|
||||
{ name: 'refreshToken', value: refreshToken, domain: 'localhost', path: '/', httpOnly: true, secure: false, sameSite: 'Lax' },
|
||||
{ name: 'csrfToken', value: csrfToken, domain: 'localhost', path: '/', httpOnly: false, secure: false, sameSite: 'Lax' },
|
||||
{ name: 'sidebar_state', value: 'false', domain: 'localhost', path: '/', httpOnly: false, secure: false, sameSite: 'Lax' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a fresh refresh token + DB session row for a client user, keyed
|
||||
* by email (not a hardcoded user_id — those drift as test accounts get
|
||||
* recreated, see chibistar/automation/starr/config.cjs history).
|
||||
*/
|
||||
async function generateClientSession(email) {
|
||||
const user_id = await getUserIdByEmail(email);
|
||||
const refreshToken = jwt.sign({ user_id }, JWT_REFRESH_SECRET, { expiresIn: '7d' });
|
||||
const tokenHash = crypto.createHash('sha256').update(refreshToken).digest('hex');
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO user_sessions (user_id, refresh_token_hash, is_active, login_info, "createdAt", "updatedAt")
|
||||
VALUES (:uid, :hash, true, '{}', NOW(), NOW())`,
|
||||
{ replacements: { uid: user_id, hash: tokenHash }, type: sequelize.QueryTypes.INSERT },
|
||||
);
|
||||
|
||||
return { user_id, refreshToken };
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate as admin via cookie injection (same mechanism as
|
||||
* generateClientSession), not the UI login form.
|
||||
*
|
||||
* REAL FINDING (2026-08-25): the admin account (k80308392@gmail.com) is
|
||||
* Google-OAuth-only now — submitting the UI email/password form returns
|
||||
* "Login failed — Please log in with Google." This matches a prior finding
|
||||
* that the *live*-domain admin session had gone Google-only, but it turns
|
||||
* out to be true here against localhost too, so the email+password+OTP flow
|
||||
* this helper originally ported from chibistar/automation/starr/config.cjs
|
||||
* (loginAsAdmin) is dead — forging the session cookie is the only way in now.
|
||||
*/
|
||||
async function loginAsAdmin(page) {
|
||||
const { refreshToken } = await generateClientSession(USERS.admin.email);
|
||||
await page.context().addCookies(buildCookies(refreshToken));
|
||||
await page.goto(`${APP_URL}/admin`, { waitUntil: 'networkidle' });
|
||||
if (/\/login/.test(page.url())) {
|
||||
throw new Error('Cookie-based admin auth bounced to /login — check JWT_REFRESH_SECRET / user_sessions wiring.');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { buildCookies, generateClientSession, loginAsAdmin };
|
||||
@@ -0,0 +1,37 @@
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Real monitor resolution instead of a hardcoded viewport — this suite runs
|
||||
// on whatever box the self-hosted runner/dev machine happens to be (varies:
|
||||
// 1366x768, 1920x1080, ...). Memoized per process.
|
||||
let _cachedViewport = null;
|
||||
|
||||
function detectViewport() {
|
||||
if (_cachedViewport) return _cachedViewport;
|
||||
const FALLBACK = { width: 1366, height: 768 };
|
||||
let detected = null;
|
||||
try {
|
||||
if (process.platform === 'linux') {
|
||||
const out = execSync('xrandr --current 2>/dev/null', { encoding: 'utf8' });
|
||||
const m = out.match(/(\d+)x(\d+)\s+[\d.]+\*/);
|
||||
if (m) detected = { width: Number(m[1]), height: Number(m[2]) };
|
||||
} else if (process.platform === 'win32') {
|
||||
const out = execSync(
|
||||
'powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; ' +
|
||||
'$b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; Write-Output \\"$($b.Width)x$($b.Height)\\""',
|
||||
{ encoding: 'utf8', windowsHide: true },
|
||||
).trim();
|
||||
const m = out.match(/(\d+)x(\d+)/);
|
||||
if (m) detected = { width: Number(m[1]), height: Number(m[2]) };
|
||||
} else if (process.platform === 'darwin') {
|
||||
const out = execSync('system_profiler SPDisplaysDataType 2>/dev/null', { encoding: 'utf8' });
|
||||
const m = out.match(/Resolution:\s*(\d+)\s*x\s*(\d+)/);
|
||||
if (m) detected = { width: Number(m[1]), height: Number(m[2]) };
|
||||
}
|
||||
} catch {
|
||||
// No xrandr on a headless CI box, PowerShell blocked, etc. — fall back.
|
||||
}
|
||||
_cachedViewport = detected ?? FALLBACK;
|
||||
return _cachedViewport;
|
||||
}
|
||||
|
||||
module.exports = { detectViewport };
|
||||
Reference in New Issue
Block a user