Skip to content

Commit 6c49af2

Browse files
committed
feat(tests): setup test suite and add basic tests
1 parent fd2ea49 commit 6c49af2

8 files changed

Lines changed: 5237 additions & 428 deletions

File tree

.github/workflows/ci.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,30 @@ on:
22
push:
33
branches:
44
- main
5+
pull_request:
6+
branches:
7+
- "*"
58

6-
name: release-please
9+
name: ci
710
jobs:
11+
test:
12+
name: Test
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Set up Node.js
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: "22"
20+
- name: Install dependencies
21+
run: npm ci
22+
- name: Run tests
23+
run: npm test
24+
825
release-please:
926
name: Release
1027
runs-on: ubuntu-latest
28+
needs: test
1129
outputs:
1230
release_created: ${{ steps.release.outputs.release_created }}
1331
tag_name: ${{ steps.release.outputs.tag_name }}

jest.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
testMatch: ['**/tests/**/*.test.ts'],
5+
moduleFileExtensions: ['ts', 'js', 'json'],
6+
verbose: true,
7+
};

package-lock.json

Lines changed: 5075 additions & 426 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@
1010
"dotenv": "^17.0.0"
1111
},
1212
"devDependencies": {
13-
"@types/node": "^22.15.29"
13+
"@types/jest": "^30.0.0",
14+
"@types/node": "^22.15.29",
15+
"jest": "^30.0.5",
16+
"ts-jest": "^29.4.1",
17+
"ts-node": "^10.9.2"
18+
},
19+
"scripts": {
20+
"test": "jest"
1421
},
1522
"version": "0.5.0"
1623
}

tests/actual-service.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
2+
import { ActualService } from '../src/services/actual-service';
3+
4+
describe('ActualService', () => {
5+
const serverURL = 'http://localhost';
6+
const password = 'password';
7+
const e2eEncryptionPassword = 'e2e';
8+
const syncId = 'sync_id';
9+
let service: ActualService;
10+
11+
beforeEach(() => {
12+
service = new ActualService(serverURL, password, e2eEncryptionPassword, syncId);
13+
});
14+
15+
it('can be instantiated', () => {
16+
expect(service).toBeInstanceOf(ActualService);
17+
});
18+
19+
it('initialize, importTransactions, and shutdown can be called (mocked)', async () => {
20+
service.initialize = jest.fn<() => Promise<void>>().mockResolvedValue(undefined);
21+
service.importTransactions = jest.fn<(accountId: string, transactions: any[]) => Promise<void>>().mockResolvedValue(undefined);
22+
service.shutdown = jest.fn<() => Promise<void>>().mockResolvedValue(undefined);
23+
await expect(service.initialize()).resolves.toBeUndefined();
24+
await expect(service.importTransactions('acc', [])).resolves.toBeUndefined();
25+
await expect(service.shutdown()).resolves.toBeUndefined();
26+
});
27+
});

tests/akahu-service.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
2+
import { AkahuService } from "../src/services/akahu-service";
3+
4+
describe("AkahuService", () => {
5+
const appToken = "app_token";
6+
const userToken = "user_token";
7+
let service: AkahuService;
8+
9+
beforeEach(() => {
10+
service = new AkahuService(appToken, userToken);
11+
});
12+
13+
it("can be instantiated", () => {
14+
expect(service).toBeInstanceOf(AkahuService);
15+
});
16+
17+
it("getTransactions can be called (mocked)", async () => {
18+
service.getTransactions = jest
19+
.fn<() => Promise<any[]>>()
20+
.mockResolvedValue([]);
21+
await expect(
22+
service.getTransactions({
23+
start: "2025-08-01",
24+
end: "2025-08-04",
25+
} as any),
26+
).resolves.toEqual([]);
27+
});
28+
});

tests/env-validator.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, it, expect, beforeEach } from "@jest/globals";
2+
import { validateEnv } from "../src/utils/env-validator";
3+
4+
describe("validateEnv", () => {
5+
beforeEach(() => {
6+
process.env.AKAHU_APP_TOKEN = "app_token";
7+
process.env.AKAHU_USER_TOKEN = "user_token";
8+
process.env.ACTUAL_SERVER_URL = "http://localhost";
9+
process.env.ACTUAL_PASSWORD = "password";
10+
process.env.ACTUAL_SYNC_ID = "sync_id";
11+
process.env.ACCOUNT_MAPPINGS = JSON.stringify({ foo: "bar" });
12+
process.env.DAYS_TO_FETCH = "7";
13+
});
14+
15+
it("returns validated config when env is valid", () => {
16+
const config = validateEnv();
17+
expect(config.akahuAppToken).toBe("app_token");
18+
expect(config.accountMappings).toEqual({ foo: "bar" });
19+
expect(config.daysToFetch).toBe(7);
20+
});
21+
22+
it("throws if required env var is missing", () => {
23+
delete process.env.AKAHU_APP_TOKEN;
24+
expect(() => validateEnv()).toThrow(/AKAHU_APP_TOKEN is not set/);
25+
});
26+
27+
it("throws if ACCOUNT_MAPPINGS is empty", () => {
28+
process.env.ACCOUNT_MAPPINGS = JSON.stringify({});
29+
expect(() => validateEnv()).toThrow(/ACCOUNT_MAPPINGS is empty/);
30+
});
31+
32+
it("throws if DAYS_TO_FETCH is invalid", () => {
33+
process.env.DAYS_TO_FETCH = "not_a_number";
34+
expect(() => validateEnv()).toThrow(
35+
/DAYS_TO_FETCH must be a positive number/,
36+
);
37+
});
38+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, it, expect } from "@jest/globals";
2+
import { transformTransaction } from "../src/utils/transaction-transformer";
3+
4+
describe("transformTransaction", () => {
5+
it("transforms an enriched transaction to actual transaction", () => {
6+
const enriched = {
7+
_id: "tx1",
8+
date: "2025-08-01",
9+
amount: 12.34,
10+
merchant: { name: "Coffee Shop" },
11+
description: "Latte",
12+
type: "debit",
13+
category: { name: "Food" },
14+
};
15+
const actual = transformTransaction(enriched as any);
16+
expect(actual.imported_id).toBe("tx1");
17+
expect(actual.date).toEqual(new Date("2025-08-01"));
18+
expect(actual.amount).toBe(1234);
19+
expect(actual.payee_name).toBe("Coffee Shop");
20+
expect(actual.notes).toMatch(/debit/);
21+
});
22+
23+
it("falls back to description if merchant missing", () => {
24+
const enriched = {
25+
_id: "tx2",
26+
date: "2025-08-01",
27+
amount: 10,
28+
description: "Groceries",
29+
type: "debit",
30+
category: { name: "Food" },
31+
};
32+
const actual = transformTransaction(enriched as any);
33+
expect(actual.payee_name).toBe("Groceries");
34+
});
35+
});

0 commit comments

Comments
 (0)