better runner

This commit is contained in:
boufni95 2025-03-11 22:32:53 +00:00
parent cf0e66712d
commit 7d693247c0
4 changed files with 119 additions and 62 deletions

View file

@ -78,12 +78,19 @@ export const LoadMainSettingsFromEnv = (): MainSettings => {
} }
} }
export const GetTestStorageSettings = (s?: StorageSettings): StorageSettings => {
if (s) {
return { dbSettings: { ...s.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath: s.eventLogPath, dataDir: "test-data" }
}
return { dbSettings: { databaseFile: ":memory:", metricsDatabaseFile: ":memory:", migrate: true }, eventLogPath: "logs/eventLogV3Test.csv", dataDir: "test-data" }
}
export const LoadTestSettingsFromEnv = (): TestSettings => { export const LoadTestSettingsFromEnv = (): TestSettings => {
const eventLogPath = `logs/eventLogV3Test${Date.now()}.csv` const eventLogPath = `logs/eventLogV3Test${Date.now()}.csv`
const settings = LoadMainSettingsFromEnv() const settings = LoadMainSettingsFromEnv()
return { return {
...settings, ...settings,
storageSettings: { dbSettings: { ...settings.storageSettings.dbSettings, databaseFile: ":memory:", metricsDatabaseFile: ":memory:" }, eventLogPath, dataDir: "data" }, storageSettings: GetTestStorageSettings(settings.storageSettings),
lndSettings: { lndSettings: {
...settings.lndSettings, ...settings.lndSettings,
otherNode: { otherNode: {

View file

@ -4,7 +4,7 @@ import { AppData, initMainHandler } from '../services/main/init.js'
import Main from '../services/main/index.js' import Main from '../services/main/index.js'
import Storage from '../services/storage/index.js' import Storage from '../services/storage/index.js'
import { User } from '../services/storage/entity/User.js' import { User } from '../services/storage/entity/User.js'
import { LoadMainSettingsFromEnv, LoadTestSettingsFromEnv, MainSettings } from '../services/main/settings.js' import { GetTestStorageSettings, LoadMainSettingsFromEnv, LoadTestSettingsFromEnv, MainSettings } from '../services/main/settings.js'
import chaiString from 'chai-string' import chaiString from 'chai-string'
import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js' import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
import SanityChecker from '../services/main/sanityChecker.js' import SanityChecker from '../services/main/sanityChecker.js'
@ -35,6 +35,27 @@ export type TestBase = {
d: Describe d: Describe
} }
export type StorageTestBase = {
expect: Chai.ExpectStatic;
storage: Storage
d: Describe
}
export const setupStorageTest = async (d: Describe): Promise<StorageTestBase> => {
const settings = GetTestStorageSettings()
const storageManager = new Storage(settings)
await storageManager.Connect(console.log)
return {
expect,
storage: storageManager,
d
}
}
export const teardownStorageTest = async (T: StorageTestBase) => {
T.storage.Stop()
}
export const SetupTest = async (d: Describe): Promise<TestBase> => { export const SetupTest = async (d: Describe): Promise<TestBase> => {
const settings = LoadTestSettingsFromEnv() const settings = LoadTestSettingsFromEnv()
const initialized = await initMainHandler(getLogger({ component: "mainForTest" }), settings) const initialized = await initMainHandler(getLogger({ component: "mainForTest" }), settings)

View file

@ -1,11 +1,12 @@
//import whyIsNodeRunning from 'why-is-node-running' //import whyIsNodeRunning from 'why-is-node-running'
import { globby } from 'globby' import { globby } from 'globby'
import { setupNetwork } from './networkSetup.js' import { setupNetwork } from './networkSetup.js'
import { Describe, SetupTest, teardown, TestBase } from './testBase.js' import { Describe, SetupTest, teardown, TestBase, StorageTestBase, setupStorageTest, teardownStorageTest } from './testBase.js'
type TestModule = { type TestModule = {
ignore?: boolean ignore?: boolean
dev?: boolean dev?: boolean
default: (T: TestBase) => Promise<void> requires?: 'storage' | '*'
default: (T: TestBase | StorageTestBase) => Promise<void>
} }
let failures = 0 let failures = 0
const getDescribe = (fileName: string): Describe => { const getDescribe = (fileName: string): Describe => {
@ -20,7 +21,6 @@ const getDescribe = (fileName: string): Describe => {
} }
const start = async () => { const start = async () => {
await setupNetwork()
const files = await globby(["**/*.spec.js", "!**/node_modules/**"]) const files = await globby(["**/*.spec.js", "!**/node_modules/**"])
const modules: { file: string, module: TestModule }[] = [] const modules: { file: string, module: TestModule }[] = []
let devModule = -1 let devModule = -1
@ -37,7 +37,13 @@ const start = async () => {
} }
if (devModule !== -1) { if (devModule !== -1) {
console.log("running dev module") console.log("running dev module")
await runTestFile(modules[devModule].file, modules[devModule].module) const { file, module } = modules[devModule]
if (module.requires === 'storage') {
console.log("dev module requires only storage, skipping network setup")
} else {
await setupNetwork()
}
await runTestFile(file, module)
} else { } else {
console.log("running all tests") console.log("running all tests")
for (const { file, module } of modules) { for (const { file, module } of modules) {
@ -65,17 +71,28 @@ const runTestFile = async (fileName: string, mod: TestModule) => {
if (mod.dev) { if (mod.dev) {
d("-----running only this file-----") d("-----running only this file-----")
} }
const T = await SetupTest(d) let T: TestBase | StorageTestBase
if (mod.requires === 'storage') {
d("-----requires only storage-----")
T = await setupStorageTest(d)
} else {
d("-----requires all-----")
T = await SetupTest(d)
}
try { try {
d("test starting") d("test starting")
await mod.default(T) await mod.default(T)
d("test finished")
await teardown(T)
} catch (e: any) { } catch (e: any) {
d(e, true) d(e, true)
d("test crashed", true) d("test crashed", true)
await teardown(T) } finally {
if (mod.requires === 'storage') {
await teardownStorageTest(T as StorageTestBase)
} else {
await teardown(T as TestBase)
}
} }
d("test finished")
if (mod.dev) { if (mod.dev) {
d("dev mod is not allowed to in CI, failing for precaution", true) d("dev mod is not allowed to in CI, failing for precaution", true)
} }

View file

@ -1,101 +1,113 @@
import { User } from '../services/storage/entity/User.js' import { User } from '../services/storage/entity/User.js'
import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js' import { defaultInvoiceExpiry } from '../services/storage/paymentStorage.js'
import { runSanityCheck, safelySetUserBalance, TestBase } from './testBase.js' import { runSanityCheck, safelySetUserBalance, StorageTestBase, TestBase } from './testBase.js'
import { FindOptionsWhere } from 'typeorm' import { FindOptionsWhere } from 'typeorm'
export const ignore = false export const ignore = false
export const dev = false export const dev = true
export const storageOnly = false export const requires = 'storage'
export default async (T: TestBase) => { export default async (T: StorageTestBase) => {
T.main.storage.dbs.setDebug(true) const u = await testCanCreateUser(T)
await testCanReadUser(T) await testCanReadUser(T, u)
await testConcurrentReads(T) await testConcurrentReads(T, u)
await testTransactionIsolation(T) T.storage.dbs.setDebug(true)
await testTransactionIsolation(T, u)
T.storage.dbs.setDebug(false)
await testUserCRUD(T) await testUserCRUD(T)
await testErrorHandling(T) await testErrorHandling(T, u)
} }
const testCanReadUser = async (T: TestBase) => { const testCanCreateUser = async (T: StorageTestBase) => {
T.d('Starting testCanReadUser') T.d('Starting testCanCreateUser')
const u = await T.main.storage.dbs.FindOne<User>('User', { where: { user_id: T.user1.userId } }) const u = await T.storage.dbs.CreateAndSave<User>('User', {
user_id: 'test-user-' + Date.now(),
balance_sats: 0,
locked: false,
})
T.expect(u).to.not.be.equal(null) T.expect(u).to.not.be.equal(null)
T.expect(u?.user_id).to.be.equal(T.user1.userId) T.d('Finished testCanCreateUser')
return u
}
const testCanReadUser = async (T: StorageTestBase, user: User) => {
T.d('Starting testCanReadUser')
const u = await T.storage.dbs.FindOne<User>('User', { where: { user_id: user.user_id } })
T.expect(u).to.not.be.equal(null)
T.expect(u?.user_id).to.be.equal(user.user_id)
T.d('Finished testCanReadUser') T.d('Finished testCanReadUser')
} }
const testConcurrentReads = async (T: TestBase) => { const testConcurrentReads = async (T: StorageTestBase, user: User) => {
T.d('Starting testConcurrentReads') T.d('Starting testConcurrentReads')
// Test multiple concurrent read operations // Test multiple concurrent read operations
const promises = [ const promises = [
T.main.storage.dbs.FindOne<User>('User', { where: { user_id: T.user1.userId } }), T.storage.dbs.FindOne<User>('User', { where: { user_id: user.user_id } }),
T.main.storage.dbs.FindOne<User>('User', { where: { user_id: T.user2.userId } }), T.storage.dbs.Find<User>('User', {})
T.main.storage.dbs.Find<User>('User', {})
] as const ] as const
const results = await Promise.all(promises) const results = await Promise.all(promises)
// Type assertions to handle possible null values // Type assertions to handle possible null values
const [user1, user2, allUsers] = results const [user1, allUsers] = results
T.expect(user1?.user_id).to.be.equal(T.user1.userId) T.expect(user1?.user_id).to.be.equal(user.user_id)
T.expect(user2?.user_id).to.be.equal(T.user2.userId)
T.expect(allUsers).to.not.be.equal(null) T.expect(allUsers).to.not.be.equal(null)
T.expect(allUsers.length).to.be.greaterThan(1) T.expect(allUsers.length).to.be.equal(1)
T.d('Finished testConcurrentReads') T.d('Finished testConcurrentReads')
} }
const testTransactionIsolation = async (T: TestBase) => { const testTransactionIsolation = async (T: StorageTestBase, user: User) => {
T.d('Starting testTransactionIsolation') T.d('Starting testTransactionIsolation')
// Start a transaction // Start a transaction
// Check initial balance before transaction // Check initial balance before transaction
const userBefore = await T.main.storage.dbs.FindOne<User>('User', { const userBefore = await T.storage.dbs.FindOne<User>('User', {
where: { user_id: T.user1.userId } where: { user_id: user.user_id }
}) })
T.expect(userBefore?.balance_sats).to.not.equal(1000, 'User should not start with balance of 1000') T.expect(userBefore?.balance_sats).to.not.equal(1000, 'User should not start with balance of 1000')
const txId = await T.main.storage.dbs.StartTx('test-transaction') const txId = await T.storage.dbs.StartTx('test-transaction')
try { try {
// Update user balance in transaction // Update user balance in transaction
const initialBalance = 1000 const initialBalance = 1000
const where: FindOptionsWhere<User> = { user_id: T.user1.userId } const where: FindOptionsWhere<User> = { user_id: user.user_id }
await T.main.storage.dbs.Update<User>('User', await T.storage.dbs.Update<User>('User',
where, where,
{ balance_sats: initialBalance }, { balance_sats: initialBalance },
txId txId
) )
// Verify balance is updated in transaction // Verify balance is updated in transaction
const userInTx = await T.main.storage.dbs.FindOne<User>('User', const userInTx = await T.storage.dbs.FindOne<User>('User',
{ where }, { where },
txId txId
) )
T.expect(userInTx?.balance_sats).to.be.equal(initialBalance) T.expect(userInTx?.balance_sats).to.be.equal(initialBalance)
// Verify balance is not visible outside transaction // Verify balance is not visible outside transaction
const userOutsideTx = await T.main.storage.dbs.FindOne<User>('User', const userOutsideTx = await T.storage.dbs.FindOne<User>('User',
{ where } { where }
) )
T.expect(userOutsideTx?.balance_sats).to.not.equal(initialBalance) T.expect(userOutsideTx?.balance_sats).to.not.equal(initialBalance)
// Commit the transaction // Commit the transaction
await T.main.storage.dbs.EndTx(txId, true, null) await T.storage.dbs.EndTx(txId, true, null)
// Verify balance is now visible // Verify balance is now visible
const userAfterCommit = await T.main.storage.dbs.FindOne<User>('User', const userAfterCommit = await T.storage.dbs.FindOne<User>('User',
{ where } { where }
) )
T.expect(userAfterCommit?.balance_sats).to.be.equal(initialBalance) T.expect(userAfterCommit?.balance_sats).to.be.equal(initialBalance)
} catch (error) { } catch (error) {
// Rollback on error // Rollback on error
await T.main.storage.dbs.EndTx(txId, false, error instanceof Error ? error.message : 'Unknown error') await T.storage.dbs.EndTx(txId, false, error instanceof Error ? error.message : 'Unknown error')
throw error throw error
} }
T.d('Finished testTransactionIsolation') T.d('Finished testTransactionIsolation')
} }
const testUserCRUD = async (T: TestBase) => { const testUserCRUD = async (T: StorageTestBase) => {
T.d('Starting testUserCRUD') T.d('Starting testUserCRUD')
// Create // Create
const newUser = { const newUser = {
@ -104,10 +116,10 @@ const testUserCRUD = async (T: TestBase) => {
locked: false, locked: false,
} }
await T.main.storage.dbs.CreateAndSave<User>('User', newUser) await T.storage.dbs.CreateAndSave<User>('User', newUser)
// Read // Read
const createdUser = await T.main.storage.dbs.FindOne<User>('User', const createdUser = await T.storage.dbs.FindOne<User>('User',
{ where: { user_id: newUser.user_id } as FindOptionsWhere<User> } { where: { user_id: newUser.user_id } as FindOptionsWhere<User> }
) )
T.expect(createdUser).to.not.be.equal(null) T.expect(createdUser).to.not.be.equal(null)
@ -115,40 +127,40 @@ const testUserCRUD = async (T: TestBase) => {
// Update // Update
const newBalance = 500 const newBalance = 500
await T.main.storage.dbs.Update<User>('User', await T.storage.dbs.Update<User>('User',
{ user_id: newUser.user_id } as FindOptionsWhere<User>, { user_id: newUser.user_id } as FindOptionsWhere<User>,
{ balance_sats: newBalance } { balance_sats: newBalance }
) )
const updatedUser = await T.main.storage.dbs.FindOne<User>('User', const updatedUser = await T.storage.dbs.FindOne<User>('User',
{ where: { user_id: newUser.user_id } as FindOptionsWhere<User> } { where: { user_id: newUser.user_id } as FindOptionsWhere<User> }
) )
T.expect(updatedUser?.balance_sats).to.be.equal(newBalance) T.expect(updatedUser?.balance_sats).to.be.equal(newBalance)
// Delete // Delete
await T.main.storage.dbs.Delete<User>('User', await T.storage.dbs.Delete<User>('User',
{ user_id: newUser.user_id } as FindOptionsWhere<User> { user_id: newUser.user_id } as FindOptionsWhere<User>
) )
const deletedUser = await T.main.storage.dbs.FindOne<User>('User', const deletedUser = await T.storage.dbs.FindOne<User>('User',
{ where: { user_id: newUser.user_id } as FindOptionsWhere<User> } { where: { user_id: newUser.user_id } as FindOptionsWhere<User> }
) )
T.expect(deletedUser).to.be.equal(null) T.expect(deletedUser).to.be.equal(null)
T.d('Finished testUserCRUD') T.d('Finished testUserCRUD')
} }
const testErrorHandling = async (T: TestBase) => { const testErrorHandling = async (T: StorageTestBase, user: User) => {
T.d('Starting testErrorHandling') T.d('Starting testErrorHandling')
// Test null result (not an error) // Test null result (not an error)
const nonExistentUser = await T.main.storage.dbs.FindOne<User>('User', const nonExistentUser = await T.storage.dbs.FindOne<User>('User',
{ where: { user_id: 'does-not-exist' } as FindOptionsWhere<User> } { where: { user_id: 'does-not-exist' } as FindOptionsWhere<User> }
) )
T.expect(nonExistentUser).to.be.equal(null) T.expect(nonExistentUser).to.be.equal(null)
// Test actual error case - invalid column name should throw an error // Test actual error case - invalid column name should throw an error
try { try {
await T.main.storage.dbs.Update<User>('User', await T.storage.dbs.Update<User>('User',
{ user_id: T.user1.userId } as FindOptionsWhere<User>, { user_id: user.user_id } as FindOptionsWhere<User>,
{ nonexistent_column: 'value' } as any { nonexistent_column: 'value' } as any
) )
T.expect.fail('Should have thrown an error') T.expect.fail('Should have thrown an error')
@ -157,24 +169,24 @@ const testErrorHandling = async (T: TestBase) => {
} }
// Test transaction rollback // Test transaction rollback
const txId = await T.main.storage.dbs.StartTx('test-error-transaction') const txId = await T.storage.dbs.StartTx('test-error-transaction')
try { try {
// Try to update with an invalid column which should cause an error // Try to update with an invalid column which should cause an error
await T.main.storage.dbs.Update<User>('User', await T.storage.dbs.Update<User>('User',
{ user_id: T.user1.userId } as FindOptionsWhere<User>, { user_id: user.user_id } as FindOptionsWhere<User>,
{ invalid_column: 'test' } as any, { invalid_column: 'test' } as any,
txId txId
) )
await T.main.storage.dbs.EndTx(txId, false, 'Rolling back test transaction') await T.storage.dbs.EndTx(txId, false, 'Rolling back test transaction')
// Verify no changes were made // Verify no changes were made
const user = await T.main.storage.dbs.FindOne<User>('User', const userAfterTx = await T.storage.dbs.FindOne<User>('User',
{ where: { user_id: T.user1.userId } } { where: { user_id: user.user_id } }
) )
T.expect(user).to.not.be.equal(null) T.expect(userAfterTx).to.not.be.equal(null)
T.expect((user as any).invalid_column).to.be.equal(undefined) T.expect((userAfterTx as any).invalid_column).to.be.equal(undefined)
} catch (error) { } catch (error) {
await T.main.storage.dbs.EndTx(txId, false, error instanceof Error ? error.message : 'Unknown error') await T.storage.dbs.EndTx(txId, false, error instanceof Error ? error.message : 'Unknown error')
T.expect(error).to.not.be.equal(null) T.expect(error).to.not.be.equal(null)
} }
T.d('Finished testErrorHandling') T.d('Finished testErrorHandling')