mirror of https://github.com/ghostfolio/ghostfolio
53 changed files with 439 additions and 520 deletions
@ -0,0 +1,140 @@ |
|||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; |
|||
import { |
|||
activityDummyData, |
|||
loadExportFile, |
|||
symbolProfileDummyData, |
|||
userDummyData |
|||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; |
|||
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; |
|||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; |
|||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; |
|||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; |
|||
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; |
|||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
|||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; |
|||
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; |
|||
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; |
|||
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; |
|||
import { parseDate } from '@ghostfolio/common/helper'; |
|||
import { ExportResponse } from '@ghostfolio/common/interfaces'; |
|||
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; |
|||
|
|||
import { Big } from 'big.js'; |
|||
import { join } from 'node:path'; |
|||
|
|||
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { |
|||
return { |
|||
CurrentRateService: jest.fn().mockImplementation(() => { |
|||
return CurrentRateServiceMock; |
|||
}) |
|||
}; |
|||
}); |
|||
|
|||
jest.mock( |
|||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service', |
|||
() => { |
|||
return { |
|||
ExchangeRateDataService: jest.fn().mockImplementation(() => { |
|||
return ExchangeRateDataServiceMock; |
|||
}) |
|||
}; |
|||
} |
|||
); |
|||
|
|||
jest.mock( |
|||
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', |
|||
() => { |
|||
return { |
|||
PortfolioSnapshotService: jest.fn().mockImplementation(() => { |
|||
return PortfolioSnapshotServiceMock; |
|||
}) |
|||
}; |
|||
} |
|||
); |
|||
|
|||
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { |
|||
return { |
|||
RedisCacheService: jest.fn().mockImplementation(() => { |
|||
return RedisCacheServiceMock; |
|||
}) |
|||
}; |
|||
}); |
|||
|
|||
describe('PortfolioCalculator', () => { |
|||
let exportResponse: ExportResponse; |
|||
|
|||
let configurationService: ConfigurationService; |
|||
let currentRateService: CurrentRateService; |
|||
let exchangeRateDataService: ExchangeRateDataService; |
|||
let portfolioCalculatorFactory: PortfolioCalculatorFactory; |
|||
let portfolioSnapshotService: PortfolioSnapshotService; |
|||
let redisCacheService: RedisCacheService; |
|||
|
|||
beforeAll(() => { |
|||
exportResponse = loadExportFile( |
|||
join(__dirname, '../../../../../../../test/import/ok/btceur.json') |
|||
); |
|||
}); |
|||
|
|||
beforeEach(() => { |
|||
configurationService = new ConfigurationService(); |
|||
|
|||
currentRateService = new CurrentRateService(null, null, null, null); |
|||
|
|||
exchangeRateDataService = new ExchangeRateDataService( |
|||
null, |
|||
null, |
|||
null, |
|||
null |
|||
); |
|||
|
|||
portfolioSnapshotService = new PortfolioSnapshotService(null); |
|||
|
|||
redisCacheService = new RedisCacheService(null, null); |
|||
|
|||
portfolioCalculatorFactory = new PortfolioCalculatorFactory( |
|||
configurationService, |
|||
currentRateService, |
|||
exchangeRateDataService, |
|||
portfolioSnapshotService, |
|||
redisCacheService |
|||
); |
|||
}); |
|||
|
|||
describe('get current positions', () => { |
|||
it.only('with BTCUSD buy (in EUR)', async () => { |
|||
jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime()); |
|||
|
|||
const activities: Activity[] = exportResponse.activities.map( |
|||
(activity) => ({ |
|||
...activityDummyData, |
|||
...activity, |
|||
date: parseDate(activity.date), |
|||
feeInAssetProfileCurrency: 4.46, |
|||
SymbolProfile: { |
|||
...symbolProfileDummyData, |
|||
currency: 'USD', |
|||
dataSource: activity.dataSource, |
|||
name: 'Bitcoin', |
|||
symbol: activity.symbol |
|||
}, |
|||
unitPriceInAssetProfileCurrency: 44558.42 |
|||
}) |
|||
); |
|||
|
|||
const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ |
|||
activities, |
|||
calculationType: PerformanceCalculationType.ROAI, |
|||
currency: 'EUR', |
|||
userId: userDummyData.id |
|||
}); |
|||
|
|||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); |
|||
|
|||
expect(portfolioSnapshot.positions[0].fee).toEqual(new Big(4.46)); |
|||
expect( |
|||
portfolioSnapshot.positions[0].feeInBaseCurrency.toNumber() |
|||
).toBeCloseTo(3.94, 1); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -1,8 +1,8 @@ |
|||
import { AdminUsers } from '@ghostfolio/common/interfaces'; |
|||
import { AdminUsersResponse } from '@ghostfolio/common/interfaces'; |
|||
|
|||
export interface UserDetailDialogParams { |
|||
deviceType: string; |
|||
hasPermissionForSubscription: boolean; |
|||
locale: string; |
|||
userData: AdminUsers['users'][0]; |
|||
userData: AdminUsersResponse['users'][0]; |
|||
} |
|||
|
|||
@ -1,56 +0,0 @@ |
|||
import { OAuthResponse } from '@ghostfolio/common/interfaces'; |
|||
|
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { Injectable, OnDestroy } from '@angular/core'; |
|||
import { AuthClient } from '@dfinity/auth-client'; |
|||
import { EMPTY, Subject } from 'rxjs'; |
|||
import { catchError, takeUntil } from 'rxjs/operators'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class InternetIdentityService implements OnDestroy { |
|||
private unsubscribeSubject = new Subject<void>(); |
|||
|
|||
public constructor(private http: HttpClient) {} |
|||
|
|||
public async login(): Promise<OAuthResponse> { |
|||
const authClient = await AuthClient.create({ |
|||
idleOptions: { |
|||
disableDefaultIdleCallback: true, |
|||
disableIdle: true |
|||
} |
|||
}); |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
authClient.login({ |
|||
onError: async () => { |
|||
return reject(); |
|||
}, |
|||
onSuccess: () => { |
|||
const principalId = authClient.getIdentity().getPrincipal(); |
|||
|
|||
this.http |
|||
.post<OAuthResponse>(`/api/v1/auth/internet-identity`, { |
|||
principalId: principalId.toText() |
|||
}) |
|||
.pipe( |
|||
catchError(() => { |
|||
reject(); |
|||
return EMPTY; |
|||
}), |
|||
takeUntil(this.unsubscribeSubject) |
|||
) |
|||
.subscribe((response) => { |
|||
resolve(response); |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
this.unsubscribeSubject.next(); |
|||
this.unsubscribeSubject.complete(); |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,4 @@ |
|||
$dark-primary-text: rgba(black, 0.87); |
|||
$light-primary-text: white; |
|||
|
|||
$mat-css-dark-theme-selector: '.theme-dark'; |
|||
@ -1,6 +1,6 @@ |
|||
import { Role } from '@prisma/client'; |
|||
|
|||
export interface AdminUsers { |
|||
export interface AdminUsersResponse { |
|||
count: number; |
|||
users: { |
|||
accountCount: number; |
|||
Loading…
Reference in new issue