Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee51cb7e2a | |||
| 40120c7027 | |||
| cfd5b8a4f3 | |||
| 3b93e328d5 | |||
| 1fd858287d | |||
| e370f5bbb7 | |||
| 3e492475c0 | |||
| c454e85ad4 | |||
| 487322abb5 | |||
| f78c521dc4 | |||
| ff9bcd782f | |||
| 1ccf515ca2 | |||
| 1b0f9c134c | |||
| 3589242996 | |||
| 689aa4d50b | |||
| 26370c03c4 |
@@ -111,7 +111,7 @@ class CurrencyRate extends Model
|
|||||||
*
|
*
|
||||||
* @return array<string, float>
|
* @return array<string, float>
|
||||||
*/
|
*/
|
||||||
public static function timeSeriesRates(string|array $currency, mixed $start = null, mixed $end = null): array
|
public static function timeSeriesRates(string|array|null $currency = null, mixed $start = null, mixed $end = null): array
|
||||||
{
|
{
|
||||||
if (empty($start)) {
|
if (empty($start)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -132,19 +132,30 @@ class CurrencyRate extends Model
|
|||||||
return $dateRange;
|
return $dateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$currency, $adjustment] = self::getCurrencyAliasAdjustments($currency);
|
if (is_array($currency)) {
|
||||||
|
|
||||||
|
$i = 1;
|
||||||
|
foreach ($currency as $curr) {
|
||||||
|
|
||||||
|
dispatch(fn () => self::timeSeriesRates($curr, $start, $end))->delay(now()->addSeconds(30 * $i));
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle currency alias
|
||||||
if (! empty($currency)) {
|
if (! empty($currency)) {
|
||||||
|
|
||||||
$currencies = Arr::wrap($currency);
|
[$currency, $adjustment] = self::getCurrencyAliasAdjustments($currency);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$currencies = Currency::all()->pluck('currency')->toArray();
|
$currency = Currency::all()->pluck('currency')->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get rates
|
// get rates
|
||||||
$rates = Frankfurter::setSymbols($currencies)->timeSeries($period->first(), $period->last());
|
$rates = Frankfurter::setSymbols($currency)->timeSeries($period->first(), $period->last());
|
||||||
|
|
||||||
$rates = collect(Arr::get($rates, 'rates', []))->sortKeys()->toArray();
|
$rates = collect(Arr::get($rates, 'rates', []))->sortKeys()->toArray();
|
||||||
|
|
||||||
@@ -177,15 +188,20 @@ class CurrencyRate extends Model
|
|||||||
// persist
|
// persist
|
||||||
self::chunkInsert($updates);
|
self::chunkInsert($updates);
|
||||||
|
|
||||||
|
if (is_string($currency)) {
|
||||||
|
|
||||||
return collect($updates)
|
return collect($updates)
|
||||||
->whereBetween('date', [$start, $end ?? now()])
|
->whereBetween('date', [$start, $end ?? now()])
|
||||||
->where('currency', $currency)
|
->where('currency', $currency)
|
||||||
->mapWithKeys(fn ($rate) => [
|
->mapWithKeys(fn ($rate) => [
|
||||||
$rate['date'] => $rate['rate'] * $adjustment,
|
$rate['date'] => $rate['rate'] * ($adjustment ?? 1),
|
||||||
])
|
])
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
private static function getNearestPastDate(CarbonInterface $date, array $datesOnly, array $rates): ?CarbonInterface
|
private static function getNearestPastDate(CarbonInterface $date, array $datesOnly, array $rates): ?CarbonInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -265,7 +281,7 @@ class CurrencyRate extends Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function getCurrencyAliasAdjustments($currency)
|
protected static function getCurrencyAliasAdjustments(string $currency)
|
||||||
{
|
{
|
||||||
$adjustment = 1;
|
$adjustment = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,11 @@ class DailyChange extends Model
|
|||||||
'tx1.quantity',
|
'tx1.quantity',
|
||||||
])
|
])
|
||||||
->selectRaw("(CASE
|
->selectRaw("(CASE
|
||||||
WHEN tx1.transaction_type = 'BUY'
|
WHEN
|
||||||
THEN COALESCE(cr.rate, 1)
|
tx1.transaction_type = 'BUY'
|
||||||
|
OR SUM(tx1.cost_basis_base) = 0
|
||||||
|
THEN
|
||||||
|
COALESCE(cr.rate, 1)
|
||||||
ELSE (
|
ELSE (
|
||||||
SELECT
|
SELECT
|
||||||
SUM(COALESCE(cr2.rate, 1) * buy.cost_basis_base)
|
SUM(COALESCE(cr2.rate, 1) * buy.cost_basis_base)
|
||||||
|
|||||||
@@ -302,8 +302,11 @@ class Holding extends Model
|
|||||||
])
|
])
|
||||||
->selectRaw(
|
->selectRaw(
|
||||||
"(CASE
|
"(CASE
|
||||||
WHEN transactions.transaction_type = 'BUY'
|
WHEN
|
||||||
THEN COALESCE(cr.rate, 1)
|
transactions.transaction_type = 'BUY'
|
||||||
|
OR SUM(transactions.cost_basis_base) = 0
|
||||||
|
THEN
|
||||||
|
COALESCE(cr.rate, 1)
|
||||||
ELSE (
|
ELSE (
|
||||||
SELECT
|
SELECT
|
||||||
SUM(COALESCE(cr2.rate, 1) * buy.cost_basis_base)
|
SUM(COALESCE(cr2.rate, 1) * buy.cost_basis_base)
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Models\CurrencyRate;
|
use App\Models\CurrencyRate;
|
||||||
|
use App\Models\Holding;
|
||||||
use App\Models\Transaction;
|
use App\Models\Transaction;
|
||||||
use Database\Seeders\CurrencySeeder;
|
use Database\Seeders\CurrencySeeder;
|
||||||
use Database\Seeders\MarketDataSeeder;
|
use Database\Seeders\MarketDataSeeder;
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Query\Expression;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -23,10 +25,15 @@ return new class extends Migration
|
|||||||
* Add options column to users table
|
* Add options column to users table
|
||||||
*/
|
*/
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
$table->json('options')->default(json_encode([
|
|
||||||
'locale' => config('app.locale', 'en'),
|
$locale = config('app.locale', 'en');
|
||||||
'display_currency' => config('investbrain.base_currency', 'USD'),
|
$currency = config('investbrain.base_currency', 'USD');
|
||||||
]))->after('profile_photo_path');
|
|
||||||
|
$default = config('database.default') === 'mysql'
|
||||||
|
? new Expression("(JSON_OBJECT('locale', '{$locale}', 'display_currency', '{$currency}'))")
|
||||||
|
: json_encode(['locale' => $locale, 'display_currency' => $currency]);
|
||||||
|
|
||||||
|
$table->json('options')->default($default)->after('profile_photo_path');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,17 +103,17 @@ return new class extends Migration
|
|||||||
'--force' => true,
|
'--force' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
CurrencyRate::timeSeriesRates(
|
|
||||||
'', // use fake currency to force
|
|
||||||
Transaction::min('date')
|
|
||||||
);
|
|
||||||
|
|
||||||
CurrencyRate::refreshCurrencyData();
|
|
||||||
|
|
||||||
Artisan::call('db:seed', [
|
Artisan::call('db:seed', [
|
||||||
'--class' => MarketDataSeeder::class,
|
'--class' => MarketDataSeeder::class,
|
||||||
'--force' => true,
|
'--force' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
CurrencyRate::timeSeriesRates(
|
||||||
|
Holding::all()->groupBy('market_data.currency')->keys()->toArray(),
|
||||||
|
Transaction::min('date')
|
||||||
|
);
|
||||||
|
|
||||||
|
CurrencyRate::refreshCurrencyData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ class MarketDataSeeder extends Seeder
|
|||||||
{
|
{
|
||||||
use WithoutModelEvents;
|
use WithoutModelEvents;
|
||||||
|
|
||||||
public array $rows = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the database seeds.
|
* Run the database seeds.
|
||||||
*/
|
*/
|
||||||
@@ -44,7 +42,7 @@ class MarketDataSeeder extends Seeder
|
|||||||
$meta_data = json_decode(base64_decode($data['meta_data']), true);
|
$meta_data = json_decode(base64_decode($data['meta_data']), true);
|
||||||
$meta_data['source'] = 'market_data_seeder';
|
$meta_data['source'] = 'market_data_seeder';
|
||||||
|
|
||||||
$this->rows[] = [
|
$rows[] = [
|
||||||
'symbol' => $data['symbol'],
|
'symbol' => $data['symbol'],
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
'currency' => $data['currency'],
|
'currency' => $data['currency'],
|
||||||
@@ -54,16 +52,17 @@ class MarketDataSeeder extends Seeder
|
|||||||
$rowCount++;
|
$rowCount++;
|
||||||
|
|
||||||
if ($rowCount % $chunkSize == 0) {
|
if ($rowCount % $chunkSize == 0) {
|
||||||
DB::table('market_data')->upsert($this->rows, ['symbol'], ['name', 'currency', 'meta_data']);
|
$this->bulkInsert($rows);
|
||||||
$this->rows = [];
|
$rows = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// final clean up
|
// final clean up
|
||||||
if (! empty($this->rows)) {
|
if (! empty($rows)) {
|
||||||
|
|
||||||
$this->bulkInsert($this->rows);
|
$this->bulkInsert($rows);
|
||||||
|
$rows = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the CSV file
|
// Close the CSV file
|
||||||
@@ -77,16 +76,17 @@ class MarketDataSeeder extends Seeder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bulkInsert(array $rows)
|
private function bulkInsert($rows): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
DB::table('market_data')->insertOrIgnore($rows);
|
DB::table('market_data')->upsert($rows, ['symbol'], ['name', 'currency', 'meta_data']);
|
||||||
$this->rows = [];
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|
||||||
throw new \Exception('Error: '.$e->getMessage());
|
throw new \Exception('Error: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gc_collect_cycles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -11,9 +11,9 @@ services:
|
|||||||
- 8000:80
|
- 8000:80
|
||||||
environment: # You can either use these properties OR an .env file. Do not use both!
|
environment: # You can either use these properties OR an .env file. Do not use both!
|
||||||
APP_URL: "http://localhost:8000"
|
APP_URL: "http://localhost:8000"
|
||||||
DB_CONNECTION: mysql
|
DB_CONNECTION: pgsql
|
||||||
DB_HOST: investbrain-mysql
|
DB_HOST: investbrain-pgsql
|
||||||
DB_PORT: 3306
|
DB_PORT: 5432
|
||||||
DB_DATABASE: investbrain
|
DB_DATABASE: investbrain
|
||||||
DB_USERNAME: investbrain
|
DB_USERNAME: investbrain
|
||||||
DB_PASSWORD: investbrain
|
DB_PASSWORD: investbrain
|
||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
- investbrain-storage:/var/app/storage # You can use a volume...
|
- investbrain-storage:/var/app/storage # You can use a volume...
|
||||||
# - /path/to/storage:/var/app/storage:delegated # ...or you can use a path on host
|
# - /path/to/storage:/var/app/storage:delegated # ...or you can use a path on host
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- pgsql
|
||||||
- redis
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- investbrain-network
|
- investbrain-network
|
||||||
@@ -40,22 +40,22 @@ services:
|
|||||||
- investbrain-redis:/data
|
- investbrain-redis:/data
|
||||||
networks:
|
networks:
|
||||||
- investbrain-network
|
- investbrain-network
|
||||||
mysql:
|
pgsql:
|
||||||
image: mysql:8.0
|
image: postgres:15-alpine
|
||||||
container_name: investbrain-mysql
|
container_name: investbrain-pgsql
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
environment:
|
environment:
|
||||||
MYSQL_DATABASE: ${DB_DATABASE:-investbrain}
|
POSTGRES_DB: ${DB_DATABASE:-investbrain}
|
||||||
MYSQL_USER: ${DB_USERNAME:-investbrain}
|
POSTGRES_USER: ${DB_USERNAME:-investbrain}
|
||||||
MYSQL_PASSWORD: ${DB_PASSWORD:-investbrain}
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-investbrain}
|
||||||
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-investbrain}
|
command: postgres -c log_min_messages=error
|
||||||
command:
|
|
||||||
- --cte-max-recursion-depth=25000
|
|
||||||
volumes:
|
volumes:
|
||||||
- investbrain-mysql:/var/lib/mysql
|
- investbrain-pgsql:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- investbrain-network
|
- investbrain-network
|
||||||
volumes:
|
volumes:
|
||||||
investbrain-storage:
|
investbrain-storage:
|
||||||
investbrain-redis:
|
investbrain-redis:
|
||||||
investbrain-mysql:
|
investbrain-pgsql:
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ RUN apk add --no-cache \
|
|||||||
bash \
|
bash \
|
||||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||||
&& docker-php-ext-install -j$(nproc) \
|
&& docker-php-ext-install -j$(nproc) \
|
||||||
gd pgsql zip pdo_mysql mysqli intl
|
gd pgsql zip pdo_mysql pdo_pgsql mysqli intl
|
||||||
|
|
||||||
# Remove default nginx config
|
# Remove default nginx config
|
||||||
RUN rm -rf /var/www/html \
|
RUN rm -rf /var/www/html \
|
||||||
|
|||||||
+13
-3
@@ -3,7 +3,7 @@
|
|||||||
cd /var/app
|
cd /var/app
|
||||||
|
|
||||||
# Starting Investbrain
|
# Starting Investbrain
|
||||||
echo "CiAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKICAqICBJSUkgICBOICAgTiAgViAgIFYgIEVFRUVFICBTU1NTICBUVFRUVCAgQkJCQkIgICBSUlJSICAgIEFBQUFBICBJSUkgICBOICAgTiAgKgogICogICBJICAgIE5OICBOICBWICAgViAgRSAgICAgIFMgICAgICAgVCAgICBCICAgIEIgIFIgICBSICAgQSAgIEEgICBJICAgIE5OICBOICAqCiAgKiAgIEkgICAgTiBOIE4gIFYgICBWICBFRUVFICAgU1NTUyAgICBUICAgIEJCQkJCICAgUlJSUiAgICBBQUFBQSAgIEkgICAgTiBOIE4gICoKICAqICAgSSAgICBOICBOTiAgViAgIFYgIEUgICAgICAgICAgUyAgIFQgICAgQiAgICBCICBSICBSICAgIEEgICBBICAgSSAgICBOICBOTiAgKgogICogIElJSSAgIE4gICBOICAgVlZWICAgRUVFRUUgIFNTU1MgICAgVCAgICBCQkJCQiAgIFIgICBSICAgQSAgIEEgIElJSSAgIE4gICBOICAqCiAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKICA=" | base64 -d
|
echo "CuKWhOKWliAgICAgICAg4paXIOKWjCAgICAg4paYICAK4paQIOKWm+KWjOKWjOKWjOKWiOKWjOKWm+KWmOKWnOKWmOKWm+KWjOKWm+KWmOKWgOKWjOKWjOKWm+KWjArilp/ilpbilozilozilprilpjilpnilpbiloTilozilpDilpbilpnilozilowg4paI4paM4paM4paM4paMCg==" | base64 -d
|
||||||
|
|
||||||
echo -e "\n====================== Validating environment... ====================== "
|
echo -e "\n====================== Validating environment... ====================== "
|
||||||
|
|
||||||
@@ -54,7 +54,6 @@ RETRIES=12
|
|||||||
DELAY=5
|
DELAY=5
|
||||||
run_migrations() {
|
run_migrations() {
|
||||||
sleep $DELAY
|
sleep $DELAY
|
||||||
# php artisan migrate --force
|
|
||||||
output=$(php artisan migrate --force 2>/dev/null)
|
output=$(php artisan migrate --force 2>/dev/null)
|
||||||
if [[ $? -eq 0 ]]; then
|
if [[ $? -eq 0 ]]; then
|
||||||
echo "$output"
|
echo "$output"
|
||||||
@@ -72,7 +71,18 @@ until run_migrations; do
|
|||||||
echo -e "\n > Waiting for database to be ready... retrying in $DELAY seconds. \n"
|
echo -e "\n > Waiting for database to be ready... retrying in $DELAY seconds. \n"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo -e "\n====================== Cleaning up... ====================== \n"
|
||||||
|
|
||||||
|
# Clear caches
|
||||||
|
echo $(php artisan cache:clear)
|
||||||
|
echo $(php artisan view:clear)
|
||||||
|
echo $(php artisan route:clear)
|
||||||
|
echo $(php artisan event:clear)
|
||||||
|
|
||||||
|
# Re-create caches
|
||||||
|
echo $(php artisan route:cache)
|
||||||
|
echo $(php artisan event:cache)
|
||||||
|
|
||||||
echo -e "\n====================== Spinning up Supervisor daemon... ====================== \n"
|
echo -e "\n====================== Spinning up Supervisor daemon... ====================== \n"
|
||||||
|
|
||||||
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class DailyChangeTest extends TestCase
|
|||||||
$second_transaction = Transaction::create([
|
$second_transaction = Transaction::create([
|
||||||
'symbol' => 'AAPL',
|
'symbol' => 'AAPL',
|
||||||
'portfolio_id' => $portfolio->id,
|
'portfolio_id' => $portfolio->id,
|
||||||
'date' => now()->subYears(3),
|
'date' => now()->subDays(1080), // 3 years
|
||||||
'quantity' => 1,
|
'quantity' => 1,
|
||||||
'cost_basis' => 39.89,
|
'cost_basis' => 39.89,
|
||||||
'transaction_type' => 'BUY',
|
'transaction_type' => 'BUY',
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class MarketDataTest extends TestCase
|
|||||||
'--force' => true,
|
'--force' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertEquals(14464, MarketData::count('symbol'));
|
$this->assertEquals(13187, MarketData::count('symbol'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_can_get_quote_from_provider()
|
public function test_can_get_quote_from_provider()
|
||||||
|
|||||||
@@ -225,8 +225,71 @@ class MultiCurrencyTest extends TestCase
|
|||||||
->andReturn(['rates' => $results]);
|
->andReturn(['rates' => $results]);
|
||||||
|
|
||||||
$result = CurrencyRate::timeSeriesRates('ZZZ', $start, $end);
|
$result = CurrencyRate::timeSeriesRates('ZZZ', $start, $end);
|
||||||
|
|
||||||
$this->assertEquals(count($period) - 1, count($result));
|
$this->assertEquals(count($period) - 1, count($result));
|
||||||
|
|
||||||
|
$result = CurrencyRate::all();
|
||||||
|
$this->assertEquals(count($period), count($result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_get_time_series_rates_with_null_currency()
|
||||||
|
{
|
||||||
|
|
||||||
|
$start = now()->subWeeks(2);
|
||||||
|
$end = now();
|
||||||
|
|
||||||
|
$period = CarbonPeriod::create($start, $end);
|
||||||
|
|
||||||
|
// mock response from Frankfurter
|
||||||
|
$results = [];
|
||||||
|
collect($period->copy()->filter('isWeekday'))->each(function ($date) use (&$results) {
|
||||||
|
$date = $date->toDateString();
|
||||||
|
|
||||||
|
$results[$date] = [
|
||||||
|
'FOO' => random_int(10, 150) / 1000,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
Frankfurter::expects('setSymbols')
|
||||||
|
->andReturnSelf();
|
||||||
|
Frankfurter::expects('timeSeries')
|
||||||
|
->andReturn(['rates' => $results]);
|
||||||
|
|
||||||
|
$result = CurrencyRate::timeSeriesRates(null, $start, $end);
|
||||||
|
$this->assertEquals(0, count($result));
|
||||||
|
|
||||||
|
$result = CurrencyRate::all();
|
||||||
|
$this->assertEquals(count($period), count($result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_can_get_time_series_rates_with_currencies()
|
||||||
|
{
|
||||||
|
|
||||||
|
$start = now()->subWeeks(2);
|
||||||
|
$end = now();
|
||||||
|
|
||||||
|
$period = CarbonPeriod::create($start, $end);
|
||||||
|
|
||||||
|
// mock response from Frankfurter
|
||||||
|
$results = [];
|
||||||
|
collect($period->copy()->filter('isWeekday'))->each(function ($date) use (&$results) {
|
||||||
|
$date = $date->toDateString();
|
||||||
|
|
||||||
|
$results[$date] = [
|
||||||
|
'FOO' => random_int(10, 150) / 1000,
|
||||||
|
'BAR' => random_int(10, 150) / 1000,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
Frankfurter::expects('setSymbols')
|
||||||
|
->andReturnSelf();
|
||||||
|
Frankfurter::expects('timeSeries')
|
||||||
|
->andReturn(['rates' => $results]);
|
||||||
|
|
||||||
|
$result = CurrencyRate::timeSeriesRates(null, $start, $end);
|
||||||
|
$this->assertEquals(0, count($result));
|
||||||
|
|
||||||
|
$result = CurrencyRate::all();
|
||||||
|
$this->assertEquals(count($period) * 2, count($result));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_time_series_rate_calls_are_chunked()
|
public function test_time_series_rate_calls_are_chunked()
|
||||||
|
|||||||
Reference in New Issue
Block a user