Skip to content
Closed
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
32 changes: 28 additions & 4 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
branches: [ "*" ]
push:
branches: [ "main", "development", "release/*" ]
branches: [ "main", "development", "release/*", "feature-stock-tracking", "development-combined" ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

Expand All @@ -20,7 +20,31 @@ jobs:
run: docker compose build
- name: Build test images
run: |
mv docker-compose.test.yml docker-compose.yml
docker compose build
docker compose -f docker-compose.yml -f docker-compose.test.yml build
- name: Start backend services
run: docker compose -f docker-compose.yml up -d database auth user analytics market api-gateway
- name: Run tests
run: docker compose up --abort-on-container-failure
run: |
failed=()
for service in \
auth-test \
user-test \
analytics-test \
market-test \
market-mutation-test \
market-integration-test
do
echo "::group::Running $service"
if ! docker compose -f docker-compose.yml -f docker-compose.test.yml run --rm "$service"; then
failed+=("$service")
fi
echo "::endgroup::"
done

if [ ${#failed[@]} -gt 0 ]; then
echo "Failed test services: ${failed[*]}"
exit 1
fi
- name: Stop services
if: always()
run: docker compose -f docker-compose.yml -f docker-compose.test.yml down -v --remove-orphans
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,6 @@ marimo/_static/
marimo/_lsp/
__marimo__/
app/server/services/api-gateway/bun.lock

# stryker files, these are ran with node and break git error checking - mutation testing for JS
.stryker-tmp
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:system"
}
7 changes: 4 additions & 3 deletions app/client/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ FROM oven/bun:1.3.9-slim AS builder

WORKDIR /app

COPY package.json bun.lockb .
COPY package.json bun.lockb ./
RUN --mount=type=cache,target=/root/.bun/install/cache \
bun install
bun install

COPY tsconfig.* .
COPY vite.config.ts .
COPY index.* .
COPY public ./public
COPY src ./src

FROM finus-webserver-base:local as base
RUN bun run build

FROM nginx:1.29.5-alpine3.23-perl
COPY --from=builder /app/dist /data/www
COPY --from=base /app/dist /data/www
COPY nginx.conf /etc/nginx/nginx.conf
15 changes: 15 additions & 0 deletions app/client/Dockerfile.base
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM oven/bun:1.3.9-slim

WORKDIR /app

COPY package.json bun.lockb .
RUN --mount=type=cache,target=/root/.bun/install/cache \
bun install

COPY tsconfig.* .
COPY vite.config.ts .
COPY index.* .
COPY public ./public
COPY src ./src

ENTRYPOINT [ "/bin/bash", "-c", "exit" ]
10 changes: 10 additions & 0 deletions app/client/Dockerfile.mutation.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM oven/bun:1

WORKDIR /app

COPY bun.lockb package.json ./
RUN bun add -g @stryker-mutator/core @stryker-mutator/typescript-checker

COPY . .

CMD ["bun","run", "mutate"]
8 changes: 8 additions & 0 deletions app/client/Dockerfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM finus-webserver-base:local

COPY test ./test
COPY vitest.config.ts .

ENV CI=true

ENTRYPOINT ["bun", "run", "vitest", "--run"]
250 changes: 236 additions & 14 deletions app/client/bun.lock

Large diffs are not rendered by default.

Binary file modified app/client/bun.lockb
Binary file not shown.
21 changes: 21 additions & 0 deletions app/client/docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
services:
webserver-base:
image: finus-webserver-base:local
build:
dockerfile: Dockerfile.base

user-input:
build:
dockerfile: Dockerfile.test
depends_on:
- webserver-base

mutation-tests:
build:
context: .
dockerfile: Dockerfile.mutation.test
depends_on:
- webserver-base
volumes:
- .:/app/client
working_dir: /app/client
10 changes: 9 additions & 1 deletion app/client/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
services:
webserver-base:
image: finus-webserver-base:local
build:
dockerfile: Dockerfile.base

webserver:
image: $DOCKER_HUB_USERNAME/finus-webserver:$RELEASE_VERSION
build: .
build:
dockerfile: Dockerfile
ports:
- "80:80"
depends_on:
- webserver-base
5 changes: 4 additions & 1 deletion app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"mutate": "stryker run"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
Expand All @@ -31,6 +32,8 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@stryker-mutator/core": "^9.6.0",
"@stryker-mutator/vitest-runner": "^9.6.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
Expand Down
49 changes: 48 additions & 1 deletion app/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import {
BrowserRouter as Router,
Navigate,
Expand All @@ -10,7 +10,10 @@ import LoginPage from "./pages/LoginPage";
import SignUpPage from "./pages/SignUpPage";
import type { AuthSession, AuthUser, AuthApiResponse } from "./types/authTypes";
import DashboardPage from "./pages/DashboardPage.tsx";
import MarketsPage from "./pages/MarketsPage";
import AppLayout from "./components/AppLayout.tsx";
import ProjectionPage from "./pages/ProjectionPage.tsx";
import { syncPinnedMarketsResetKey } from "./utils/marketStorage";
//import { loadSession, saveSession, clearSession } from "./utils/storage.ts";
//import { requestAuth } from "./api/AuthAPI";
//import { resolveUserFromToken } from "./utils/token";
Expand Down Expand Up @@ -139,6 +142,35 @@ function App() {
loadSession(),
);

useEffect(() => {
let cancelled = false;

async function syncServerResetKey() {
try {
const response = await fetch(`${API_BASE_URL}/client-state/reset-key`);
const data = (await response.json().catch(() => null)) as {
resetKey?: unknown;
} | null;

if (cancelled || !response.ok) {
return;
}

if (typeof data?.resetKey === "string" && data.resetKey.length > 0) {
syncPinnedMarketsResetKey(data.resetKey);
}
} catch {
// Ignore reset-key sync failures and keep the client usable offline.
}
}

void syncServerResetKey();

return () => {
cancelled = true;
};
}, []);

function handleAuthSuccess(token: string, fallbackUser: Partial<AuthUser>) {
const nextSession: AuthSession = {
token,
Expand Down Expand Up @@ -203,14 +235,29 @@ function App() {
)
}
/>

{session && (
<Route element={<AppLayout onLogout={handleLogout} />}>
<Route
path="/dashboard"
element={<DashboardPage session={session} />}
/>
<Route
path="/markets"
element={<MarketsPage session={session} />}
/>
</Route>
)}

{session && (
<Route element={<AppLayout onLogout={handleLogout} />}>
<Route
path="/projection"
element={<ProjectionPage session={session} />}
/>
</Route>
)}

<Route path="*" element={<Navigate to="/" replace />} />

{/**Code below is only used for dashboard development purposes */}
Expand Down
10 changes: 9 additions & 1 deletion app/client/src/api/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ const requestUrl = "http://localhost:3000/api/accounts";
//Sends a request to get different accounts the user has
export async function getUserAccounts(
session: AuthSession,
type?: string,
): Promise<Account[]> {
let url = requestUrl;

//Determine if we're targetting a specific type
if (type) {
url = `${requestUrl}/?type=${type}`;
}

try {
//Sends a http request and waits for a response
const response = await fetch(requestUrl, {
const response = await fetch(url, {
method: "GET",
headers: { Authorization: `Bearer ${session.token}` },
});
Expand Down
59 changes: 59 additions & 0 deletions app/client/src/api/Debt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { DebtPayoffResponse } from "../types/responseTypes";
import type { AuthSession } from "@/types/authTypes";
import type { Account } from "@/types/AccountType";
import type { projectionDebtRequest } from "@/types/requestTypes";

const requestUrl = "http://localhost:3000/api/debts";

//Sends a request to get different debts the user has
export async function getDebt(session: AuthSession): Promise<Account[]> {
try {
//Sends a http request and waits for a response
const response = await fetch(requestUrl, {
method: "GET",
headers: { Authorization: `Bearer ${session.token}` },
});

//Determine if we were able to retrieve user's data
if (!response.ok) {
//Failed to retrieve user data, return empty array
console.error("Error: Failed to retrieve users debts", response.status);
return [];
}

return response.json();
} catch (error) {
console.error(error);
throw error;
}
}

//Post request, even tho it says get in the function
export async function getDebtProjection(
session: AuthSession,
request: projectionDebtRequest,
): Promise<DebtPayoffResponse> {
const url = "http://localhost:3000/predict-debt-payoff";

try {
//Create post request and wait for response
const response = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${session.token}`,
},
body: JSON.stringify(request),
});

//Determine if our post was a success
if (!response.ok) {
console.error(response.status);
}

return response.json();
} catch (error) {
console.error(error);
throw error;
}
}
47 changes: 47 additions & 0 deletions app/client/src/api/GoalsAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { Goal } from "../types/goals.ts";
import { instance } from "./config";

async function fetchGoals(): Promise<Goal[]> {
console.log("fetching goals");
const response = await instance.get("/goals");
if (response.status !== 200) {
throw new Error(`Failed to fetch goals data: ${response.statusText}`);
}
console.log("returning a list of goals: ", response.data);
return response.data;
}

async function createGoal(goal: Partial<Goal>): Promise<Goal> {
console.log("creating goal: ", goal);
const response = await instance.post("/goals", goal);
if (response.status !== 201) {
// 201 Created is standard for POST
throw new Error(`Failed to create goal: ${response.statusText}`);
}
console.log("got response from create goal: ", response.data);
return response.data;
}

async function updateGoal(
goalId: string,
updates: Partial<Goal>,
): Promise<Goal> {
const response = await instance.patch(`/goals?gid=${goalId}`, updates);
if (response.status !== 200) {
throw new Error(`Failed to edit goal: ${response.statusText}`);
}

// console.log("updated goal: ", response.data);
return response.data;
}

async function deleteGoal(goalId: string): Promise<void> {
// console.log("deleting goal: ", goalId);
const response = await instance.delete(`/goals?gid=${goalId}`);
if (response.status !== 204) {
// 204 No Content is standard for DELETE
throw new Error(`Failed to delete goal: ${response.statusText}`);
}
}

export { fetchGoals, createGoal, updateGoal, deleteGoal };
Loading