Add caster for membership

This commit is contained in:
philipp lang 2022-03-05 20:22:09 +01:00
parent 07e5a9021c
commit d954f0d0b8
13 changed files with 296 additions and 184 deletions

View File

@ -16,6 +16,7 @@ use Zoomyboy\LaravelNami\Authentication\Authenticator;
use Zoomyboy\LaravelNami\Concerns\IsNamiMember;
use Zoomyboy\LaravelNami\Data\Baustein;
use Zoomyboy\LaravelNami\Data\Course;
use Zoomyboy\LaravelNami\Data\Membership;
use Zoomyboy\LaravelNami\Exceptions\NotAuthenticatedException;
use Zoomyboy\LaravelNami\Exceptions\RightException;
use Zoomyboy\LaravelNami\NamiException;
@ -161,14 +162,20 @@ class Api {
}
}
/**
* @return Collection<Membership>
*/
public function membershipsOf(int $memberId): Collection
{
$this->assertLoggedIn();
return $this->fetchCollection(
return $this
->fetchCollection(
'/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/'.$memberId.'/flist',
'Membership fetch failed'
);
)
->map(fn ($membership) => $this->membership($memberId, $membership['id']))
->filter(fn ($membership) => $membership !== null);
}
public function subactivitiesOf(int $activityId): Collection
@ -181,18 +188,15 @@ class Api {
)->map(fn ($subactivity) => Subactivity::fromNami($subactivity));
}
public function membership($memberId, $membershipId) {
public function membership($memberId, $membershipId): ?Membership
{
$this->assertLoggedIn();
$url = $this->url.'/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/'.$memberId.'/'.$membershipId;
$response = $this->http()->get($url);
$membership = $this->fetchData(
"/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/{$memberId}/{$membershipId}",
"Fails fetching membership {$membershipId} for {$memberId}",
);
Logger::http($url, $response, 'Single Membership '.$membershipId.' from '.$memberId, ['memberId' => $memberId]);
if($response->json()['success'] === false && Str::startsWith($response['message'], 'Sicherheitsverletzung')) {
throw new RightException('');
}
return $response->json()['data'];
return $membership ? new Membership($membership) : null;
}
public function courses(): Collection

View File

@ -0,0 +1,16 @@
<?php
namespace Zoomyboy\LaravelNami\Casters;
use Carbon\Carbon;
use Spatie\DataTransferObject\Caster;
class CarbonCaster implements Caster {
public function cast(mixed $value): Carbon
{
return Carbon::parse($value);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Zoomyboy\LaravelNami\Casters;
use Carbon\Carbon;
use Spatie\DataTransferObject\Caster;
class NullableCarbonCaster implements Caster {
public function cast(mixed $value): ?Carbon
{
return $value
? Carbon::parse($value)
: null;
}
}

34
src/Data/Membership.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace Zoomyboy\LaravelNami\Data;
use Carbon\Carbon;
use Spatie\DataTransferObject\Attributes\CastWith;
use Spatie\DataTransferObject\Attributes\MapFrom;
use Spatie\DataTransferObject\DataTransferObject;
use Zoomyboy\LaravelNami\Casters\CarbonCaster;
use Zoomyboy\LaravelNami\Casters\NullableCarbonCaster;
class Membership extends DataTransferObject {
public int $id;
#[MapFrom('gruppierungId')]
public int $groupId;
#[MapFrom('aktivVon')]
#[CastWith(CarbonCaster::class)]
public Carbon $startsAt;
#[MapFrom('aktivBis')]
#[CastWith(NullableCarbonCaster::class)]
public ?Carbon $endsAt;
#[MapFrom('taetigkeitId')]
public int $activityId;
#[MapFrom('untergliederungId')]
public int $subactivityId;
}

View File

@ -48,7 +48,7 @@ class CourseFake extends Fake {
*
* @return self
*/
public function fetchesSingle(int $memberId, array $data): self
public function shows(int $memberId, array $data): self
{
Http::fake(function($request) use ($memberId, $data) {
if ($request->url() === "https://nami.dpsg.de/ica/rest/nami/mitglied-ausbildung/filtered-for-navigation/mitglied/mitglied/{$memberId}/{$data['id']}") {
@ -59,7 +59,7 @@ class CourseFake extends Fake {
return $this;
}
public function failsFetchingSingle(int $memberId, int $courseId, string $error = 'Error'): self
public function failsShowing(int $memberId, int $courseId, string $error = 'Error'): self
{
Http::fake(function($request) use ($memberId, $courseId, $error) {
if ($request->url() === "https://nami.dpsg.de/ica/rest/nami/mitglied-ausbildung/filtered-for-navigation/mitglied/mitglied/{$memberId}/{$courseId}") {
@ -70,7 +70,7 @@ class CourseFake extends Fake {
return $this;
}
public function failsFetchingSingleWithHtml(int $memberId, int $courseId): self
public function failsShowingWithHtml(int $memberId, int $courseId): self
{
Http::fake(function($request) use ($memberId, $courseId) {
if ($request->url() === "https://nami.dpsg.de/ica/rest/nami/mitglied-ausbildung/filtered-for-navigation/mitglied/mitglied/{$memberId}/{$courseId}") {

View File

@ -19,7 +19,7 @@ class MembershipFake extends Fake {
return $this;
}
public function fetchFails(int $memberId, string $error): self
public function failsFetching(int $memberId, string $error = 'Error'): self
{
Http::fake(function($request) use ($memberId, $error) {
$url = 'https://nami.dpsg.de/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/'.$memberId.'/flist';
@ -31,6 +31,18 @@ class MembershipFake extends Fake {
return $this;
}
public function failsFetchingWithHtml(int $memberId): self
{
Http::fake(function($request) use ($memberId) {
$url = 'https://nami.dpsg.de/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/'.$memberId.'/flist';
if ($request->url() === $url && $request->method() === 'GET') {
return $this->htmlResponse();
}
});
return $this;
}
public function shows(int $memberId, array $data): self
{
Http::fake(function($request) use ($memberId, $data) {
@ -53,7 +65,7 @@ class MembershipFake extends Fake {
return $this;
}
public function failsToShow(int $memberId, int $membershipId, string $error): self
public function failsShowing(int $memberId, int $membershipId, ?string $error = 'Error'): self
{
Http::fake(function($request) use ($memberId, $membershipId, $error) {
$url = 'https://nami.dpsg.de/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/'.$memberId.'/'.$membershipId;
@ -65,4 +77,32 @@ class MembershipFake extends Fake {
return $this;
}
public function failsShowingWithHtml(int $memberId, int $membershipId): self
{
Http::fake(function($request) use ($memberId, $membershipId) {
$url = 'https://nami.dpsg.de/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/'.$memberId.'/'.$membershipId;
if ($request->url() === $url && $request->method() === 'GET') {
return $this->htmlResponse();
}
});
return $this;
}
public function assertFetched(int $memberId): void
{
Http::assertSent(function($request) use ($memberId) {
return $request->url() === "https://nami.dpsg.de/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/{$memberId}/flist"
&& $request->method() === 'GET';
});
}
public function assertFetchedSingle(int $memberId, int $membershipId): void
{
Http::assertSent(function($request) use ($memberId, $membershipId) {
return $request->url() === "https://nami.dpsg.de/ica/rest/nami/zugeordnete-taetigkeiten/filtered-for-navigation/gruppierung-mitglied/mitglied/{$memberId}/{$membershipId}"
&& $request->method() === 'GET';
});
}
}

View File

@ -4,7 +4,9 @@ namespace Zoomyboy\LaravelNami;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Zoomyboy\LaravelNami\Data\Membership;
use Zoomyboy\LaravelNami\Exceptions\RightException;
class Member extends Model {
@ -139,20 +141,12 @@ class Member extends Model {
return parent::setAttribute($key, $value);
}
public function memberships() {
$memberships = Nami::membershipsOf($this->id);
return LazyCollection::make(function() use ($memberships) {
foreach ($memberships as $membership) {
$m = $this->membership($membership['id']);
if ($m === null) {
continue;
}
yield $m;
}
});
/**
* @return Collection<Membership>
*/
public function memberships(): Collection
{
return Nami::membershipsOf($this->id);
}
public function putMembership(array $data): int

View File

@ -1,63 +0,0 @@
<?php
namespace Zoomyboy\LaravelNami;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\LazyCollection;
class Membership extends Model {
protected static $overviewAttributes = [
'id' => 'id',
'gruppierungId' => 'group_id',
'taetigkeitId' => 'activity_id',
'untergliederungId' => 'subactivity_id',
'aktivVon' => 'starts_at',
'aktivBis' => 'ends_at',
'taetigkeit' => 'activity_name',
'untergliederung' => 'subactivity_name'
];
protected $casts = [];
protected $nullable = [];
protected $guarded = [];
public static $default = [
'untergliederungId' => null,
'untergliederung' => null
];
public static function fromNami($item) {
$item = collect(static::$default)->merge(collect($item))
->only(array_keys(static::$overviewAttributes))
->mapWithKeys(function($item, $key) {
return [ data_get(static::$overviewAttributes, $key, $key) => $item ];
})
->toArray();
return (new self($item));
}
public function setAttribute($key, $value) {
if (in_array($key, $this->nullable) && $value === '') {
return parent::setAttribute($key, null);
}
return parent::setAttribute($key, $value);
}
public function setStartsAtAttribute($date) {
$this->attributes['starts_at'] = empty($date)
? null
: Carbon::parse($date)->format('Y-m-d');
}
public function setEndsAtAttribute($date) {
$this->attributes['ends_at'] = empty($date)
? null
: Carbon::parse($date)->format('Y-m-d');
}
}

View File

@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Zoomyboy\LaravelNami\Api;
use Zoomyboy\LaravelNami\Authentication\Auth;
use Zoomyboy\LaravelNami\Authentication\Authenticator;
use Zoomyboy\LaravelNami\Cookies\Cookie;
use Zoomyboy\LaravelNami\Cookies\FakeCookie;
@ -39,9 +40,18 @@ class TestCase extends \Orchestra\Testbench\TestCase
public function login(): Api
{
touch (__DIR__.'/../.cookies_test/'.time().'.txt');
Auth::fake();
Auth::success(12345, 'secret');
return Nami::login(123, 'secret');
return Nami::login(12345, 'secret');
}
public function loginWithWrongCredentials(): Api
{
Auth::fake();
Auth::failed(12345, 'wrong');
return Nami::login(12345, 'wrong');
}
protected function clearCookies(): void

View File

@ -0,0 +1,123 @@
<?php
namespace Zoomyboy\LaravelNami\Tests\Unit\Api;
use Zoomyboy\LaravelNami\Data\Membership;
use Zoomyboy\LaravelNami\Exceptions\RightException;
use Zoomyboy\LaravelNami\Fakes\MembershipFake;
use Zoomyboy\LaravelNami\Nami;
use Zoomyboy\LaravelNami\NamiException;
use Zoomyboy\LaravelNami\Tests\TestCase;
class MembershipTest extends TestCase
{
public function testGetMembershipsCount(): void
{
app(MembershipFake::class)
->fetches(6, [10, 11])
->shows(6, ['id' => 10])
->shows(6, ['id' => 11]);
$memberships = $this->login()->membershipsOf(6);
$this->assertCount(2, $memberships);
}
public function testMembershipIsInstanceOfDto(): void
{
app(MembershipFake::class)
->fetches(6, [10])
->shows(6, [
'id' => 10,
'aktivBis' => '',
'aktivVon' => '2013-05-06 00:00:00',
'gruppierungId' => 1000,
'taetigkeitId' => 15,
'untergliederungId' => 16,
]);
$membership = $this->login()->membershipsOf(6)->first();
$this->assertInstanceOf(Membership::class, $membership);
$this->assertSame(10, $membership->id);
$this->assertSame('2013-05-06 00:00:00', $membership->startsAt->toDateTimeString());
$this->assertSame(null, $membership->endsAt);
$this->assertSame(1000, $membership->groupId);
$this->assertSame(15, $membership->activityId);
$this->assertSame(16, $membership->subactivityId);
}
public function testFetchesMembership(): void
{
app(MembershipFake::class)
->fetches(6, [10])
->shows(6, ['id' => 10]);
$this->login()->membershipsOf(6);
app(MembershipFake::class)->assertFetched(6);
app(MembershipFake::class)->assertFetchedSingle(6, 10);
}
public function testThrowExceptionWhenFetchingFails(): void
{
app(MembershipFake::class)->failsFetching(6);
$this->expectException(NamiException::class);
$this->login()->membershipsOf(6);
}
public function testReturnsNothingWhenFetchingFailsWithHtml(): void
{
app(MembershipFake::class)->failsFetchingWithHtml(6);
$memberships = $this->login()->membershipsOf(6);
$this->assertCount(0, $memberships);
}
public function testThrowExceptionWhenFetchingSingleFails(): void
{
app(MembershipFake::class)
->fetches(6, [55])
->failsShowing(6, 55);
$this->expectException(NamiException::class);
$this->login()->membershipsOf(6);
}
public function testDontReturnSingleThatReturnsHtml(): void
{
app(MembershipFake::class)
->fetches(6, [55, 66])
->shows(6, ['id' => 55])
->failsShowingWithHtml(6, 66);
$memberships = $this->login()->membershipsOf(6);
$this->assertCount(1, $memberships);
}
public function testItFetchesSingleMembership(): void
{
app(MembershipFake::class)->shows(16, [ "id" => 68 ]);
$membership = $this->login()->membership(16, 68);
$this->assertSame(68, $membership->id);
}
/**
* @testWith ["Sicherheitsverletzung: Zugriff auf Rechte Recht (n:2001002 o:2) fehlgeschlagen", "Access denied - no right for requested operation"]
*/
public function test_it_gets_no_memberships_with_no_rights(string $error): void
{
app(MembershipFake::class)->failsShowing(16, 68, $error);
$membership = $this->login()->membership(16, 68);
$this->assertNull($membership);
}
}

View File

@ -14,19 +14,11 @@ use Zoomyboy\LaravelNami\Tests\TestCase;
class CourseTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
Auth::fake();
}
public function test_get_courses_of_member(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)
->fetches(11111, [788])
->fetchesSingle(11111, [
->shows(11111, [
'bausteinId' => 506,
'id' => 788,
'veranstalter' => 'KJA',
@ -34,7 +26,7 @@ class CourseTest extends TestCase
'vstgTag' => '2021-11-12 00:00:00'
]);
$course = Nami::login(12345, 'secret')->coursesFor(11111)->first();
$course = $this->login()->coursesFor(11111)->first();
$this->assertEquals(788, $course->id);
$this->assertEquals('KJA', $course->organizer);
@ -48,36 +40,33 @@ class CourseTest extends TestCase
public function test_it_gets_multiple_courses_of_member(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)
->fetches(11111, [788, 789])
->fetchesSingle(11111, ['id' => 788])
->fetchesSingle(11111, ['id' => 789]);
->shows(11111, ['id' => 788])
->shows(11111, ['id' => 789]);
$courses = Nami::login(12345, 'secret')->coursesFor(11111);
$courses = $this->login()->coursesFor(11111);
$this->assertCount(2, $courses);
}
public function test_return_nothing_when_course_returns_html(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)
->fetches(11111, [788, 789])
->failsFetchingSingleWithHtml(11111, 788)
->fetchesSingle(11111, ['id' => 789]);
->failsShowingWithHtml(11111, 788)
->shows(11111, ['id' => 789]);
$courses = Nami::login(12345, 'secret')->coursesFor(11111);
$courses = $this->login()->coursesFor(11111);
$this->assertCount(1, $courses);
}
public function test_return_empty_when_course_index_returns_html(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)->failsFetchingWithHtml(11111);
$courses = Nami::login(12345, 'secret')->coursesFor(11111);
$courses = $this->login()->coursesFor(11111);
$this->assertCount(0, $courses);
}
@ -91,9 +80,8 @@ class CourseTest extends TestCase
public function test_store_a_course(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)->createsSuccessfully(123, 999);
Nami::login(12345, 'secret')->createCourse(123, [
$this->login()->createCourse(123, [
'event_name' => '::event::',
'completed_at' => '2021-01-02 00:00:00',
'organizer' => '::org::',
@ -121,10 +109,9 @@ class CourseTest extends TestCase
public function test_update_a_course(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)->updatesSuccessfully(123, 999);
Nami::login(12345, 'secret')->updateCourse(123, 999, [
$this->login()->updateCourse(123, 999, [
'event_name' => '::event::',
'completed_at' => '2021-01-02 00:00:00',
'organizer' => '::org::',
@ -144,10 +131,9 @@ class CourseTest extends TestCase
public function test_throw_exception_when_course_update_failed(): void
{
$this->expectException(NamiException::class);
Auth::success(12345, 'secret');
app(CourseFake::class)->failsUpdating(123, 999);
Nami::login(12345, 'secret')->updateCourse(123, 999, [
$this->login()->updateCourse(123, 999, [
'event_name' => '::event::',
'completed_at' => '2021-01-02 00:00:00',
'organizer' => '::org::',
@ -158,7 +144,6 @@ class CourseTest extends TestCase
public function test_it_needs_valid_credentials_to_store_a_course(): void
{
Auth::failed(12345, 'secret');
$this->expectException(NotAuthenticatedException::class);
Nami::createCourse(123, [
'event_name' => '::event::',
@ -170,20 +155,17 @@ class CourseTest extends TestCase
public function test_it_throws_login_exception_when_fetching_with_wrong_credentials(): void
{
Auth::failed(12345, 'secret');
$this->expectException(LoginException::class);
Nami::login(12345, 'secret')->coursesFor(11111);
$this->loginWithWrongCredentials()->coursesFor(11111);
}
public function test_throw_exception_when_storing_failed(): void
{
$this->expectException(NamiException::class);
Auth::success(12345, 'secret');
app(CourseFake::class)->failsCreating(123);
Nami::login(12345, 'secret');
Nami::createCourse(123, [
$this->login()->createCourse(123, [
'event_name' => '::event::',
'completed_at' => '2021-01-02 00:00:00',
'organizer' => '::org::',
@ -193,10 +175,9 @@ class CourseTest extends TestCase
public function test_delete_a_course(): void
{
Auth::success(12345, 'secret');
app(CourseFake::class)->deletesSuccessfully(123, 999);
Nami::login(12345, 'secret')->deleteCourse(123, 999);
$this->login()->deleteCourse(123, 999);
app(CourseFake::class)->assertDeleted(123, 999);
}
@ -204,10 +185,9 @@ class CourseTest extends TestCase
public function test_shrow_exception_when_deleting_failed(): void
{
$this->expectException(NamiException::class);
Auth::success(12345, 'secret');
app(CourseFake::class)->failsDeleting(123, 999);
Nami::login(12345, 'secret')->deleteCourse(123, 999);
$this->login()->deleteCourse(123, 999);
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Zoomyboy\LaravelNami\Tests\Unit;
namespace Zoomyboy\LaravelNami\Tests\Unit\Member;
use Illuminate\Support\Facades\Http;
use Zoomyboy\LaravelNami\Fakes\MembershipFake;
@ -8,13 +8,13 @@ use Zoomyboy\LaravelNami\Member;
use Zoomyboy\LaravelNami\Nami;
use Zoomyboy\LaravelNami\Tests\TestCase;
class MemberTest extends TestCase
class MembershipTest extends TestCase
{
public function test_get_memberships_of_a_member(): void
{
app(MembershipFake::class)
->fetches(16, [68, 69])
->fetches(16, [68])
->shows(16, [
"id" => 68,
]);
@ -31,13 +31,13 @@ class MemberTest extends TestCase
*/
public function test_it_gets_no_memberships_with_no_rights(string $error): void
{
app(MembershipFake::class)->fetchFails(16, $error);
app(MembershipFake::class)->failsFetching(16, $error);
$this->login();
$member = new Member(['id' => 16]);
$memberships = $member->memberships();
$this->assertSame([], $member->memberships()->toArray());
$this->assertSame([], $memberships->toArray());
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace Zoomyboy\LaravelNami\Tests\Unit;
use Illuminate\Support\Facades\Http;
use Zoomyboy\LaravelNami\Exceptions\RightException;
use Zoomyboy\LaravelNami\Fakes\MembershipFake;
use Zoomyboy\LaravelNami\Member;
use Zoomyboy\LaravelNami\Nami;
use Zoomyboy\LaravelNami\Tests\TestCase;
class MembershipTest extends TestCase
{
public function test_get_all_memberships_of_a_member(): void
{
$data = [
"id" => 68,
"gruppierung" => "Diözesanleitung Köln 100000",
"gruppierungId" => 103,
"taetigkeit" => "ReferentIn",
"taetigkeitId" => 33,
"untergliederung" => "Pfadfinder",
"untergliederungId" => 55,
"aktivVon" => "2017-02-11 00:00:00",
"aktivBis" => "2017-03-11 00:00:00"
];
app(MembershipFake::class)->shows(16, $data);
$this->assertSame($data, $this->login()->membership(16, 68));
}
/**
* @testWith ["Sicherheitsverletzung: Zugriff auf Rechte Recht (n:2001002 o:2) fehlgeschlagen", "Access denied - no right for requested operation"]
*/
public function test_it_gets_no_memberships_with_no_rights(string $error): void
{
$this->expectException(RightException::class);
app(MembershipFake::class)->failsToShow(16, 68, $error);
$this->login()->membership(16, 68);
}
}