Merge pull request #344 from shikorism/feature/319-csv-importer
CheckinCsvImporter
This commit is contained in:
commit
e872964144
@ -12,6 +12,9 @@ class Ejaculation extends Model
|
||||
{
|
||||
use HasEagerLimit;
|
||||
|
||||
const SOURCE_WEB = 'web';
|
||||
const SOURCE_CSV = 'csv';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id', 'ejaculated_date',
|
||||
'note', 'geo_latitude', 'geo_longitude', 'link',
|
||||
|
29
app/Exceptions/CsvImportException.php
Normal file
29
app/Exceptions/CsvImportException.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class CsvImportException extends \RuntimeException
|
||||
{
|
||||
/** @var string[] */
|
||||
private $errors;
|
||||
|
||||
/**
|
||||
* CsvImportException constructor.
|
||||
* @param string[] $errors
|
||||
*/
|
||||
public function __construct(...$errors)
|
||||
{
|
||||
parent::__construct(array_first($errors));
|
||||
$this->errors = $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ class AppServiceProvider extends ServiceProvider
|
||||
Blade::directive('parsedown', function ($expression) {
|
||||
return "<?php echo app('parsedown')->text($expression); ?>";
|
||||
});
|
||||
|
||||
stream_filter_register('convert.mbstring.*', 'Stream_Filter_Mbstring');
|
||||
}
|
||||
|
||||
/**
|
||||
|
82
app/Rules/CsvDateTime.php
Normal file
82
app/Rules/CsvDateTime.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
/**
|
||||
* CSVインポート機能の日時バリデーションルール
|
||||
* @package App\Rules
|
||||
*/
|
||||
class CsvDateTime implements Rule
|
||||
{
|
||||
const VALID_FORMATS = [
|
||||
'Y/m/d H:i:s',
|
||||
'Y/n/j G:i:s',
|
||||
'Y/m/d H:i',
|
||||
'Y/n/j G:i',
|
||||
];
|
||||
|
||||
const MINIMUM_TIMESTAMP = 946652400; // 2000-01-01 00:00:00 JST
|
||||
const MAXIMUM_TIMESTAMP = 4102412399; // 2099-12-31 23:59:59 JST
|
||||
|
||||
/** @var string Validation error message */
|
||||
private $message = ':attributeの形式は "年/月/日 時:分" にしてください。';
|
||||
|
||||
/**
|
||||
* Create a new rule instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
// この辺の実装の元ネタは、LaravelのValidatesAttributes#validateDateFormat()
|
||||
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (self::VALID_FORMATS as $format) {
|
||||
$date = \DateTime::createFromFormat('!' . $format, $value);
|
||||
if (!$date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timestamp = (int) $date->format('U');
|
||||
if ($timestamp < self::MINIMUM_TIMESTAMP || self::MAXIMUM_TIMESTAMP < $timestamp) {
|
||||
$this->message = ':attributeは 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$formatted = $date->format($format);
|
||||
if ($formatted === $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
174
app/Services/CheckinCsvImporter.php
Normal file
174
app/Services/CheckinCsvImporter.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Ejaculation;
|
||||
use App\Exceptions\CsvImportException;
|
||||
use App\Rules\CsvDateTime;
|
||||
use App\Tag;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use League\Csv\Reader;
|
||||
|
||||
class CheckinCsvImporter
|
||||
{
|
||||
/** @var User Target user */
|
||||
private $user;
|
||||
/** @var string CSV filename */
|
||||
private $filename;
|
||||
|
||||
public function __construct(User $user, string $filename)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
// Guess charset
|
||||
$charset = $this->guessCharset($this->filename);
|
||||
|
||||
// Open CSV
|
||||
$csv = Reader::createFromPath($this->filename, 'r');
|
||||
$csv->setHeaderOffset(0);
|
||||
if ($charset === 'SJIS-win') {
|
||||
$csv->addStreamFilter('convert.mbstring.encoding.SJIS-win:UTF-8');
|
||||
}
|
||||
|
||||
// Import
|
||||
DB::transaction(function () use ($csv) {
|
||||
$errors = [];
|
||||
|
||||
if (!in_array('日時', $csv->getHeader(), true)) {
|
||||
$errors[] = '日時列は必須です。';
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
throw new CsvImportException(...$errors);
|
||||
}
|
||||
|
||||
foreach ($csv->getRecords() as $offset => $record) {
|
||||
$line = $offset + 1;
|
||||
$ejaculation = new Ejaculation(['user_id' => $this->user->id]);
|
||||
|
||||
$validator = Validator::make($record, [
|
||||
'日時' => ['required', new CsvDateTime()],
|
||||
'ノート' => 'nullable|string|max:500',
|
||||
'オカズリンク' => 'nullable|url|max:2000',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
foreach ($validator->errors()->all() as $message) {
|
||||
$errors[] = "{$line} 行 : {$message}";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']);
|
||||
$ejaculation->note = str_replace(["\r\n", "\r"], "\n", $record['ノート'] ?? '');
|
||||
$ejaculation->link = $record['オカズリンク'] ?? '';
|
||||
$ejaculation->source = Ejaculation::SOURCE_CSV;
|
||||
|
||||
try {
|
||||
$tags = $this->parseTags($line, $record);
|
||||
} catch (CsvImportException $e) {
|
||||
$errors = array_merge($errors, $e->getErrors());
|
||||
continue;
|
||||
}
|
||||
|
||||
$ejaculation->save();
|
||||
if (!empty($tags)) {
|
||||
$ejaculation->tags()->sync(collect($tags)->pluck('id'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
throw new CsvImportException(...$errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定されたファイルを読み込み、文字コードの判定を行います。
|
||||
* @param string $filename CSVファイル名
|
||||
* @param int $samplingLength ファイルの先頭から何バイトを判定に使用するかを指定
|
||||
* @return string 検出した文字コード (UTF-8, SJIS-win, ...)
|
||||
* @throws CsvImportException ファイルの読み込みに失敗した、文字コードを判定できなかった、または非対応文字コードを検出した場合にスロー
|
||||
*/
|
||||
private function guessCharset(string $filename, int $samplingLength = 1024): string
|
||||
{
|
||||
$fp = fopen($filename, 'rb');
|
||||
if (!$fp) {
|
||||
throw new CsvImportException('CSVファイルの読み込み中にエラーが発生しました。');
|
||||
}
|
||||
|
||||
try {
|
||||
$head = fread($fp, $samplingLength);
|
||||
if ($head === false) {
|
||||
throw new CsvImportException('CSVファイルの読み込み中にエラーが発生しました。');
|
||||
}
|
||||
|
||||
for ($addition = 0; $addition < 4; $addition++) {
|
||||
$charset = mb_detect_encoding($head, ['ASCII', 'UTF-8', 'SJIS-win'], true);
|
||||
if ($charset) {
|
||||
if (array_search($charset, ['UTF-8', 'SJIS-win'], true) === false) {
|
||||
throw new CsvImportException('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。');
|
||||
} else {
|
||||
return $charset;
|
||||
}
|
||||
}
|
||||
|
||||
// 1バイト追加で読み込んだら、文字境界に到達して上手く判定できるかもしれない
|
||||
if (feof($fp)) {
|
||||
break;
|
||||
}
|
||||
$next = fread($fp, 1);
|
||||
if ($next === false) {
|
||||
throw new CsvImportException('CSVファイルの読み込み中にエラーが発生しました。');
|
||||
}
|
||||
$head .= $next;
|
||||
}
|
||||
|
||||
throw new CsvImportException('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。');
|
||||
} finally {
|
||||
fclose($fp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* タグ列をパースします。
|
||||
* @param int $line 現在の行番号 (1 origin)
|
||||
* @param array $record 対象行のデータ
|
||||
* @return Tag[]
|
||||
* @throws CsvImportException バリデーションエラーが発生した場合にスロー
|
||||
*/
|
||||
private function parseTags(int $line, array $record): array
|
||||
{
|
||||
$tags = [];
|
||||
foreach (array_keys($record) as $column) {
|
||||
if (preg_match('/\Aタグ\d{1,2}\z/u', $column) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag = trim($record[$column]);
|
||||
if (empty($tag)) {
|
||||
continue;
|
||||
}
|
||||
if (mb_strlen($tag) > 255) {
|
||||
throw new CsvImportException("{$line} 行 : {$column}は255文字以内にしてください。");
|
||||
}
|
||||
if (strpos($tag, "\n") !== false) {
|
||||
throw new CsvImportException("{$line} 行 : {$column}に改行を含めることはできません。");
|
||||
}
|
||||
|
||||
$tags[] = Tag::firstOrCreate(['name' => $tag]);
|
||||
if (count($tags) >= 32) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
}
|
@ -4,6 +4,12 @@
|
||||
"keywords": ["framework", "laravel"],
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/xcezx/Stream_Filter_Mbstring"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1.0",
|
||||
"anhskohbo/no-captcha": "^3.0",
|
||||
@ -13,7 +19,9 @@
|
||||
"jakeasmith/http_build_url": "^1.0",
|
||||
"laravel/framework": "5.5.*",
|
||||
"laravel/tinker": "~1.0",
|
||||
"league/csv": "^9.5",
|
||||
"misd/linkify": "^1.1",
|
||||
"openpear/stream_filter_mbstring": "dev-master",
|
||||
"staudenmeir/eloquent-eager-limit": "^1.0",
|
||||
"symfony/css-selector": "^4.3",
|
||||
"symfony/dom-crawler": "^4.3"
|
||||
|
116
composer.lock
generated
116
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b6dfb80c350a7276bb2513a1aeb3d602",
|
||||
"content-hash": "a391908b8086044d943007dce1ff4f12",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anhskohbo/no-captcha",
|
||||
@ -1190,6 +1190,75 @@
|
||||
],
|
||||
"time": "2019-08-07T15:10:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/csv.git",
|
||||
"reference": "b348d09d0d258a4f068efb50a2510dc63101c213"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/csv/zipball/b348d09d0d258a4f068efb50a2510dc63101c213",
|
||||
"reference": "b348d09d0d258a4f068efb50a2510dc63101c213",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=7.0.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"friendsofphp/php-cs-fixer": "^2.12",
|
||||
"phpstan/phpstan": "^0.9.2",
|
||||
"phpstan/phpstan-phpunit": "^0.9.4",
|
||||
"phpstan/phpstan-strict-rules": "^0.9.0",
|
||||
"phpunit/phpunit": "^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Csv\\": "src"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ignace Nyamagana Butera",
|
||||
"email": "nyamsprod@gmail.com",
|
||||
"homepage": "https://github.com/nyamsprod/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Csv data manipulation made easy in PHP",
|
||||
"homepage": "http://csv.thephpleague.com",
|
||||
"keywords": [
|
||||
"csv",
|
||||
"export",
|
||||
"filter",
|
||||
"import",
|
||||
"read",
|
||||
"write"
|
||||
],
|
||||
"time": "2019-12-15T19:51:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "1.0.63",
|
||||
@ -1555,6 +1624,47 @@
|
||||
],
|
||||
"time": "2019-11-08T13:50:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "openpear/stream_filter_mbstring",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/xcezx/Stream_Filter_Mbstring.git",
|
||||
"reference": "1c5ab27fd874e74d3d2bfdb9b74d3ebe017e6e14"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/xcezx/Stream_Filter_Mbstring/zipball/1c5ab27fd874e74d3d2bfdb9b74d3ebe017e6e14",
|
||||
"reference": "1c5ab27fd874e74d3d2bfdb9b74d3ebe017e6e14",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Stream_Filter_Mbstring": "src/"
|
||||
}
|
||||
},
|
||||
"license": [
|
||||
"PHP-3.01"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "hnw",
|
||||
"role": "developer"
|
||||
},
|
||||
{
|
||||
"name": "MAEKAWA Tsuyoshi",
|
||||
"email": "main.xcezx@gmail.com",
|
||||
"role": "maintainer"
|
||||
}
|
||||
],
|
||||
"description": "mbstring を使って文字列変換を行う stream filter",
|
||||
"support": {
|
||||
"source": "https://github.com/xcezx/Stream_Filter_Mbstring/tree/master",
|
||||
"issues": "https://github.com/xcezx/Stream_Filter_Mbstring/issues"
|
||||
},
|
||||
"time": "2012-11-21T12:10:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v9.99.99",
|
||||
@ -6410,7 +6520,9 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"openpear/stream_filter_mbstring": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddSourceToEjaculations extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('ejaculation_sources', function (Blueprint $table) {
|
||||
$table->string('name');
|
||||
$table->primary('name');
|
||||
});
|
||||
Schema::table('ejaculations', function (Blueprint $table) {
|
||||
$table->string('source')->nullable();
|
||||
$table->foreign('source')->references('name')->on('ejaculation_sources');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('ejaculations', function (Blueprint $table) {
|
||||
$table->dropColumn('source');
|
||||
});
|
||||
Schema::drop('ejaculation_sources');
|
||||
}
|
||||
}
|
@ -11,6 +11,6 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// $this->call(UsersTableSeeder::class);
|
||||
$this->call(EjaculationSourcesSeeder::class);
|
||||
}
|
||||
}
|
||||
|
19
database/seeds/EjaculationSourcesSeeder.php
Normal file
19
database/seeds/EjaculationSourcesSeeder.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class EjaculationSourcesSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$sources = ['web', 'csv'];
|
||||
foreach ($sources as $source) {
|
||||
DB::table('ejaculation_sources')->insert(['name' => $source]);
|
||||
}
|
||||
}
|
||||
}
|
268
tests/Unit/Services/CheckinCsvImporterTest.php
Normal file
268
tests/Unit/Services/CheckinCsvImporterTest.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Ejaculation;
|
||||
use App\Exceptions\CsvImportException;
|
||||
use App\Services\CheckinCsvImporter;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CheckinCsvImporterTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->seed();
|
||||
}
|
||||
|
||||
public function testIncompatibleCharsetEUCJP()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv');
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideMissingTime
|
||||
*/
|
||||
public function testMissingTime($filename)
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('日時列は必須です。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, $filename);
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
public function provideMissingTime()
|
||||
{
|
||||
return [
|
||||
'UTF8' => [__DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'],
|
||||
'SJIS' => [__DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDate
|
||||
*/
|
||||
public function testDate($expectedDate, $filename)
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, $filename);
|
||||
$importer->execute();
|
||||
$ejaculation = $user->ejaculations()->first();
|
||||
|
||||
$this->assertSame(1, $user->ejaculations()->count());
|
||||
$this->assertEquals($expectedDate, $ejaculation->ejaculated_date);
|
||||
}
|
||||
|
||||
public function provideDate()
|
||||
{
|
||||
$date = Carbon::create(2020, 1, 23, 6, 1, 0, 'Asia/Tokyo');
|
||||
|
||||
return [
|
||||
'Zero, Second, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date.utf8.csv'],
|
||||
'NoZero, Second, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nozero.utf8.csv'],
|
||||
'Zero, NoSecond, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nosecond.utf8.csv'],
|
||||
'NoZero, NoSecond, UTF8' => [$date, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testInvalidDate()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
try {
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/invalid-date.utf8.csv');
|
||||
$importer->execute();
|
||||
} catch (CsvImportException $e) {
|
||||
$this->assertSame('2 行 : 日時は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[0]);
|
||||
$this->assertSame('3 行 : 日時は 2000/01/01 00:00 〜 2099/12/31 23:59 の間のみ対応しています。', $e->getErrors()[1]);
|
||||
$this->assertSame('4 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[2]);
|
||||
$this->assertSame('5 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[3]);
|
||||
$this->assertSame('6 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[4]);
|
||||
$this->assertSame('7 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[5]);
|
||||
$this->assertSame('8 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[6]);
|
||||
$this->assertSame('9 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[7]);
|
||||
$this->assertSame('10 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[8]);
|
||||
$this->assertSame('11 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[9]);
|
||||
$this->assertSame('12 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[10]);
|
||||
$this->assertSame('13 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[11]);
|
||||
$this->assertSame('14 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[12]);
|
||||
$this->assertSame('15 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[13]);
|
||||
$this->assertSame('16 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[14]);
|
||||
$this->assertSame('17 行 : 日時の形式は "年/月/日 時:分" にしてください。', $e->getErrors()[15]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fail('期待する例外が発生していません');
|
||||
}
|
||||
|
||||
public function testNoteUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/note.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculations = $user->ejaculations()->orderBy('ejaculated_date')->get();
|
||||
|
||||
$this->assertCount(3, $ejaculations);
|
||||
$this->assertEquals('The quick brown fox jumps over the lazy dog. 素早い茶色の狐はのろまな犬を飛び越える', $ejaculations[0]->note);
|
||||
$this->assertEquals("The quick brown fox jumps over the lazy dog.\n素早い茶色の狐はのろまな犬を飛び越える", $ejaculations[1]->note);
|
||||
$this->assertEquals('The quick brown fox jumps over the "lazy" dog.', $ejaculations[2]->note);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideNoteOverLength
|
||||
*/
|
||||
public function testNoteOverLength($filename)
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('2 行 : ノートには500文字以下の文字列を指定してください。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, $filename);
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
public function provideNoteOverLength()
|
||||
{
|
||||
return [
|
||||
'ASCII Only, UTF8' => [__DIR__ . '/../../fixture/Csv/note-over-length.ascii.utf8.csv'],
|
||||
'JP, UTF8' => [__DIR__ . '/../../fixture/Csv/note-over-length.jp.utf8.csv'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testLinkUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculations = $user->ejaculations()->orderBy('ejaculated_date')->get();
|
||||
|
||||
$this->assertCount(1, $ejaculations);
|
||||
$this->assertEquals('http://example.com', $ejaculations[0]->link);
|
||||
}
|
||||
|
||||
public function testLinkOverLengthUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('3 行 : オカズリンクには2000文字以下の文字列を指定してください。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link-over-length.utf8.csv');
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
public function testLinkIsNotUrlUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('2 行 : オカズリンクには正しい形式のURLを指定してください。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/link-not-url.utf8.csv');
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
public function testTag1UTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag1.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculation = $user->ejaculations()->first();
|
||||
$tags = $ejaculation->tags()->get();
|
||||
|
||||
$this->assertSame(1, $user->ejaculations()->count());
|
||||
$this->assertCount(1, $tags);
|
||||
$this->assertEquals('貧乳', $tags[0]->name);
|
||||
}
|
||||
|
||||
public function testTag2UTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag2.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculation = $user->ejaculations()->first();
|
||||
$tags = $ejaculation->tags()->get();
|
||||
|
||||
$this->assertSame(1, $user->ejaculations()->count());
|
||||
$this->assertCount(2, $tags);
|
||||
$this->assertEquals('貧乳', $tags[0]->name);
|
||||
$this->assertEquals('巨乳', $tags[1]->name);
|
||||
}
|
||||
|
||||
public function testTagOverLengthUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('3 行 : タグ1は255文字以内にしてください。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-over-length.utf8.csv');
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
public function testTagCanAcceptJumpedColumnUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-jumped-column.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculation = $user->ejaculations()->first();
|
||||
$tags = $ejaculation->tags()->get();
|
||||
|
||||
$this->assertSame(1, $user->ejaculations()->count());
|
||||
$this->assertCount(2, $tags);
|
||||
$this->assertEquals('貧乳', $tags[0]->name);
|
||||
$this->assertEquals('巨乳', $tags[1]->name);
|
||||
}
|
||||
|
||||
public function testTagCantAcceptMultilineUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$this->expectException(CsvImportException::class);
|
||||
$this->expectExceptionMessage('2 行 : タグ1に改行を含めることはできません。');
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-multiline.utf8.csv');
|
||||
$importer->execute();
|
||||
}
|
||||
|
||||
public function testTagCanAccept32ColumnsUTF8()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/tag-33-column.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculation = $user->ejaculations()->first();
|
||||
$tags = $ejaculation->tags()->get();
|
||||
|
||||
$this->assertSame(1, $user->ejaculations()->count());
|
||||
$this->assertCount(32, $tags);
|
||||
$this->assertEquals('み', $tags[31]->name);
|
||||
}
|
||||
|
||||
public function testSourceIsCsv()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date.utf8.csv');
|
||||
$importer->execute();
|
||||
$ejaculation = $user->ejaculations()->first();
|
||||
|
||||
$this->assertSame(1, $user->ejaculations()->count());
|
||||
$this->assertEquals(Ejaculation::SOURCE_CSV, $ejaculation->source);
|
||||
}
|
||||
}
|
2
tests/fixture/Csv/.editorconfig
vendored
Normal file
2
tests/fixture/Csv/.editorconfig
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
[*.csv]
|
||||
end_of_line = crlf
|
2
tests/fixture/Csv/date-nosecond.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/date-nosecond.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時
|
||||
2020/01/23 06:01
|
|
2
tests/fixture/Csv/date-nozero-nosecond.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/date-nozero-nosecond.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時
|
||||
2020/1/23 6:01
|
|
2
tests/fixture/Csv/date-nozero.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/date-nozero.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時
|
||||
2020/1/23 6:01:02
|
|
2
tests/fixture/Csv/date.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/date.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時
|
||||
2020/01/23 06:01:02
|
|
2
tests/fixture/Csv/incompatible-charset.eucjp.csv
vendored
Normal file
2
tests/fixture/Csv/incompatible-charset.eucjp.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,ノート,オカズリンク
|
||||
2019/1/01 0:01,テストテストあああああ,https://example.com/
|
|
17
tests/fixture/Csv/invalid-date.utf8.csv
vendored
Normal file
17
tests/fixture/Csv/invalid-date.utf8.csv
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
日時,ノート
|
||||
1999/12/31 23:59:59,最小境界
|
||||
2100/01/01 00:00:00,最大境界
|
||||
-1/01/01 00:00:00,存在しない日付
|
||||
2019/-1/01 00:00:00,存在しない日付
|
||||
2019/01/-1 00:00:00,存在しない日付
|
||||
2019/02/29 00:00:00,存在しない日付
|
||||
2019/00/01 00:00:00,存在しない日付
|
||||
2019/01/00 00:00:00,存在しない日付
|
||||
2019/01/32 00:00:00,存在しない日付
|
||||
2019/13/01 00:00:00,存在しない日付
|
||||
2019/01/01 00:60:00,存在しない時刻
|
||||
2019/01/01 24:00:00,存在しない時刻
|
||||
2019/01/01 00:00:60,存在しない時刻
|
||||
2019/01/01 -1:00:00,存在しない時刻
|
||||
2019/01/01 00:-1:00,存在しない時刻
|
||||
2019/01/01 00:00:-1,存在しない時刻
|
|
3
tests/fixture/Csv/link-not-url.utf8.csv
vendored
Normal file
3
tests/fixture/Csv/link-not-url.utf8.csv
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
日時,オカズリンク
|
||||
2020/01/23 06:01,example
|
||||
|
|
3
tests/fixture/Csv/link-over-length.utf8.csv
vendored
Normal file
3
tests/fixture/Csv/link-over-length.utf8.csv
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
日時,オカズリンク
|
||||
2020/01/23 06:01,https://example.com/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/exam
|
||||
2020/01/23 06:01,https://example.com/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/example/examp
|
|
2
tests/fixture/Csv/link.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/link.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,オカズリンク
|
||||
2020/01/23 06:01,http://example.com
|
|
1
tests/fixture/Csv/missing-time.sjis.csv
vendored
Normal file
1
tests/fixture/Csv/missing-time.sjis.csv
vendored
Normal file
@ -0,0 +1 @@
|
||||
ノート,オカズリンク,タグ1
|
|
1
tests/fixture/Csv/missing-time.utf8.csv
vendored
Normal file
1
tests/fixture/Csv/missing-time.utf8.csv
vendored
Normal file
@ -0,0 +1 @@
|
||||
ノート,オカズリンク,タグ1
|
|
2
tests/fixture/Csv/note-over-length.ascii.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/note-over-length.ascii.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,ノート
|
||||
2020/01/23 06:01,oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooox
|
|
2
tests/fixture/Csv/note-over-length.jp.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/note-over-length.jp.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,ノート
|
||||
2020/01/23 06:01,ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああい
|
|
5
tests/fixture/Csv/note.utf8.csv
vendored
Normal file
5
tests/fixture/Csv/note.utf8.csv
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
日時,ノート
|
||||
2020/01/23 06:01,The quick brown fox jumps over the lazy dog. 素早い茶色の狐はのろまな犬を飛び越える
|
||||
2020/01/23 06:02,"The quick brown fox jumps over the lazy dog.
|
||||
素早い茶色の狐はのろまな犬を飛び越える"
|
||||
2020/01/23 06:03,"The quick brown fox jumps over the ""lazy"" dog."
|
|
2
tests/fixture/Csv/tag-33-column.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/tag-33-column.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,タグ1,タグ2,タグ3,タグ4,タグ5,タグ6,タグ7,タグ8,タグ9,タグ10,タグ11,タグ12,タグ13,タグ14,タグ15,タグ16,タグ17,タグ18,タグ19,タグ20,タグ21,タグ22,タグ23,タグ24,タグ25,タグ26,タグ27,タグ28,タグ29,タグ30,タグ31,タグ32,タグ33
|
||||
2020/01/23 06:01,あ,い,う,え,お,か,き,く,け,こ,さ,し,す,せ,そ,た,ち,つ,て,と,な,に,ぬ,ね,の,は,ひ,ふ,へ,ほ,ま,み,む
|
|
2
tests/fixture/Csv/tag-jumped-column.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/tag-jumped-column.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,タグ1,タグ3
|
||||
2020/01/23 06:01,貧乳,巨乳
|
|
3
tests/fixture/Csv/tag-multiline.utf8.csv
vendored
Normal file
3
tests/fixture/Csv/tag-multiline.utf8.csv
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
日時,タグ1
|
||||
2020/01/23 06:01,"複数行の
|
||||
タグ"
|
|
3
tests/fixture/Csv/tag-over-length.utf8.csv
vendored
Normal file
3
tests/fixture/Csv/tag-over-length.utf8.csv
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
日時,タグ1
|
||||
2020/01/23 06:01,ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||
2020/01/23 06:02,ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooox
|
|
2
tests/fixture/Csv/tag1.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/tag1.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,タグ1
|
||||
2020/01/23 06:01,貧乳
|
|
2
tests/fixture/Csv/tag2.utf8.csv
vendored
Normal file
2
tests/fixture/Csv/tag2.utf8.csv
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
日時,タグ1,タグ2
|
||||
2020/01/23 06:01,貧乳,巨乳
|
|
Loading…
Reference in New Issue
Block a user