diff --git a/app/Io/CheckinCsvImporter.php b/app/Io/CheckinCsvImporter.php index 1fd2f0d..2a7bb91 100644 --- a/app/Io/CheckinCsvImporter.php +++ b/app/Io/CheckinCsvImporter.php @@ -4,18 +4,24 @@ namespace App\Io; 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(string $filename) + public function __construct(User $user, string $filename) { + $this->user = $user; $this->filename = $filename; } @@ -45,48 +51,24 @@ class CheckinCsvImporter } foreach ($csv->getRecords() as $offset => $record) { - $ejaculation = new Ejaculation(); + $ejaculation = new Ejaculation(['user_id' => $this->user->id]); - $checkinAt = $record['日時'] ?? null; - if (empty($checkinAt)) { - $errors[] = "{$offset} 行 : 日時列は必須です。"; - continue; - } - if (preg_match('/\A20\d{2}[-/](1[0-2]|0?\d)[-/](0?\d|[1-2]\d|3[01]) (0?\d|1\d|2[0-4]):(0?\d|[1-5]\d)(:(0?\d|[1-5]\d))?\z/', $checkinAt) !== 1) { - $errors[] = "{$offset} 行 : 日時列の書式が正しくありません。"; - continue; - } - $checkinAt = str_replace('/', '-', $checkinAt); - try { - $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y-m-d H:i+', $checkinAt); - } catch (\InvalidArgumentException $e) { - $errors[] = "{$offset} 行 : 日時列に不正な値が入力されています。"; - continue; - } + $validator = Validator::make($record, [ + '日時' => ['required', new CsvDateTime()], + 'ノート' => 'nullable|string|max:500', + 'オカズリンク' => 'nullable|url|max:2000', + ]); - if (!empty($record['ノート'])) { - $note = $record['ノート']; - - if (mb_strlen($note) > 500) { - $errors[] = "{$offset} 行 : ノート列は500文字以内にしてください。"; - continue; + if ($validator->fails()) { + foreach ($validator->errors()->all() as $message) { + $errors[] = "{$offset} 行 : {$message}"; } - - $ejaculation->note = $note; + continue; } - if (!empty($record['オカズリンク'])) { - $link = $record['オカズリンク']; - - if (mb_strlen($link) > 2000) { - $errors[] = "{$offset} 行 : オカズリンク列は500文字以内にしてください。"; - continue; - } - - // TODO: URL Validation - - $ejaculation->link = $link; - } + $ejaculation->ejaculated_date = Carbon::createFromFormat('!Y/m/d H:i+', $record['日時']); + $ejaculation->note = $record['ノート'] ?? ''; + $ejaculation->link = $record['オカズリンク'] ?? ''; $tagIds = []; for ($i = 1; $i <= 32; $i++) { diff --git a/app/Rules/CsvDateTime.php b/app/Rules/CsvDateTime.php new file mode 100644 index 0000000..118c5ba --- /dev/null +++ b/app/Rules/CsvDateTime.php @@ -0,0 +1,65 @@ +format($format) === $value) { + return true; + } + } + + return false; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return ':attribute の形式は "年/月/日 時:分" にしてください。'; + } +} diff --git a/tests/Unit/Io/CheckinCsvImporterTest.php b/tests/Unit/Io/CheckinCsvImporterTest.php index a85935d..3a81ded 100644 --- a/tests/Unit/Io/CheckinCsvImporterTest.php +++ b/tests/Unit/Io/CheckinCsvImporterTest.php @@ -4,34 +4,90 @@ namespace Tests\Unit\Io; use App\Exceptions\CsvImportException; use App\Io\CheckinCsvImporter; +use App\User; +use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Carbon; use Tests\TestCase; class CheckinCsvImporterTest extends TestCase { + use RefreshDatabase; + public function testIncompatibleCharsetEUCJP() { + $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('文字コード判定に失敗しました。UTF-8 (BOM無し) または Shift_JIS をお使いください。'); - $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv'); + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/incompatible-charset.eucjp.csv'); $importer->execute(); } public function testMissingTimeUTF8() { + $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('日時列は必須です。'); - $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.utf8.csv'); $importer->execute(); } public function testMissingTimeSJIS() { + $user = factory(User::class)->create(); $this->expectException(CsvImportException::class); $this->expectExceptionMessage('日時列は必須です。'); - $importer = new CheckinCsvImporter(__DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/missing-time.sjis.csv'); $importer->execute(); } + + public function testDateNoSecondUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nosecond.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } + + public function testDateNoZeroNoSecondUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero-nosecond.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } + + public function testDateUTF8() + { + $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(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } + + public function testDateNoZeroUTF8() + { + $user = factory(User::class)->create(); + + $importer = new CheckinCsvImporter($user, __DIR__ . '/../../fixture/Csv/date-nozero.utf8.csv'); + $importer->execute(); + $ejaculation = $user->ejaculations()->first(); + + $this->assertSame(1, $user->ejaculations()->count()); + $this->assertEquals(Carbon::create(2020, 1, 23, 6, 1, 0), $ejaculation->ejaculated_date); + } } diff --git a/tests/fixture/Csv/date-nosecond.utf8.csv b/tests/fixture/Csv/date-nosecond.utf8.csv new file mode 100644 index 0000000..a100b02 --- /dev/null +++ b/tests/fixture/Csv/date-nosecond.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/01/23 06:01 diff --git a/tests/fixture/Csv/date-nozero-nosecond.utf8.csv b/tests/fixture/Csv/date-nozero-nosecond.utf8.csv new file mode 100644 index 0000000..60cb295 --- /dev/null +++ b/tests/fixture/Csv/date-nozero-nosecond.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/1/23 6:01 diff --git a/tests/fixture/Csv/date-nozero.utf8.csv b/tests/fixture/Csv/date-nozero.utf8.csv new file mode 100644 index 0000000..e0b3ede --- /dev/null +++ b/tests/fixture/Csv/date-nozero.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/1/23 6:01:02 diff --git a/tests/fixture/Csv/date.utf8.csv b/tests/fixture/Csv/date.utf8.csv new file mode 100644 index 0000000..24ee353 --- /dev/null +++ b/tests/fixture/Csv/date.utf8.csv @@ -0,0 +1,2 @@ +日時 +2020/01/23 06:01:02 diff --git a/tests/fixture/Csv/incompatible-charset.eucjp.csv b/tests/fixture/Csv/incompatible-charset.eucjp.csv index 866ed4d..62dcc8d 100644 --- a/tests/fixture/Csv/incompatible-charset.eucjp.csv +++ b/tests/fixture/Csv/incompatible-charset.eucjp.csv @@ -1,2 +1,2 @@ ,Ρ, -2019-01-01 00:01:02,ƥȥƥȤ,https://example.com/ +2019/1/01 0:01,ƥȥƥȤ,https://example.com/