Skip to content

Commit 628400d

Browse files
committed
✅ Adds Orcid data import optimization feature tests
Includes ProfileDataFactory for publications and authors, publications mock and feature test.
1 parent cacd441 commit 628400d

File tree

5 files changed

+293
-3
lines changed

5 files changed

+293
-3
lines changed

app/Profile.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,10 @@ public function hasOrcidManagedPublications()
177177

178178
public function updateORCID()
179179
{
180-
$publicationsManager = new OrcidPublicationsRepository($this);
181-
182-
return $publicationsManager->syncPublications();
180+
$publications_manager = app()->make(OrcidPublicationsRepository::class);
181+
$publications_manager->setProfile($this);
182+
183+
return $publications_manager->syncPublications();
183184
}
184185

185186

app/Providers/RepositoryServiceProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Providers;
44

5+
use App\Profile;
56
use App\Repositories\Contracts\PublicationsRepositoryContract;
67
use App\Repositories\OrcidPublicationsRepository;
78
use Illuminate\Support\ServiceProvider;
@@ -25,6 +26,9 @@ public function boot()
2526
*/
2627
public function register()
2728
{
29+
$this->app->bind(OrcidPublicationsRepository::class, function($app) {
30+
return new OrcidPublicationsRepository($app[Profile::class]);
31+
});
2832
$this->app->bind(PublicationsRepositoryContract::class, OrcidPublicationsRepository::class);
2933
}
3034
}

database/factories/ProfileDataFactory.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\Profile;
66
use App\ProfileData;
7+
use App\Helpers\Publication;
78
use Illuminate\Database\Eloquent\Factories\Factory;
89
use Illuminate\Support\Arr;
910

@@ -29,6 +30,33 @@ class ProfileDataFactory extends Factory
2930
'Library',
3031
];
3132

33+
protected $existing_publications_data =
34+
[
35+
0 => [
36+
'sort_order' => '2021',
37+
'title' => 'Existing Publication Title #1',
38+
],
39+
1 => [
40+
'sort_order' => '2022',
41+
'title' => 'Existing Publication Title #2',
42+
],
43+
2 => [
44+
'sort_order' => '2023',
45+
'title' => 'Existing Publication Title #3',
46+
],
47+
];
48+
49+
protected $authors_names_patterns =
50+
[
51+
'return ($this->faker->unique()->lastName() . ", " . strtoupper($this->faker->randomLetter()) . ". " . strtoupper($this->faker->randomLetter()) . ".");',
52+
'return ($this->faker->unique()->lastName() . ", " . strtoupper($this->faker->randomLetter()). ".");',
53+
'return ($this->faker->firstName() . " " . $this->faker->lastName());',
54+
'return ($this->faker->lastName() . " " . strtoupper($this->faker->randomLetter()) . strtoupper($this->faker->randomLetter()));',
55+
'return ($this->faker->lastName() . " " . strtoupper($this->faker->randomLetter()));',
56+
];
57+
58+
public int $authors_count = 5;
59+
3260
/**
3361
* Define the model's default state.
3462
*
@@ -180,4 +208,75 @@ public function news()
180208
];
181209
});
182210
}
211+
212+
/**
213+
* Data Type "publications" with pre-defined sort_order and title
214+
*
215+
* @return \Illuminate\Database\Eloquent\Factories\Factory
216+
*/
217+
public function existing_publication($type, $profile = null)
218+
{
219+
return $this
220+
->count(3)
221+
->state(['type' => 'publications'])
222+
->$type($profile)
223+
->sequence(function($sequence) {
224+
return [
225+
'sort_order' => $this->existing_publications_data[$sequence->index]['sort_order'],
226+
'data->title' => $this->existing_publications_data[$sequence->index]['title'],
227+
];
228+
});
229+
}
230+
231+
/**
232+
* Data Type "publications" sourced from the Orcid API. Formatted, and ready to sync with ProfileData
233+
*
234+
* @return \Illuminate\Database\Eloquent\Factories\Factory
235+
*/
236+
public function orcid_publication(Profile $profile = null) {
237+
return $this->state(function (array $attributes) use ($profile) {
238+
239+
$authors = $this->authorsNames();
240+
241+
return [
242+
'profile_id' => $profile->id,
243+
'sort_order' => $this->faker->year(),
244+
'data' => [
245+
'put-code' => $this->faker->numberBetween(99000,100000),
246+
'url' => $this->faker->url(),
247+
'title' => $this->faker->sentence(),
248+
'year' => $this->faker->year(),
249+
'type' => 'Journal',
250+
'month' => $this->faker->month(),
251+
'day' => $this->faker->dayOfMonth(),
252+
'journal_title' => $this->faker->sentence(),
253+
'doi' => $this->faker->regexify(config('app.DOI_REGEX')),
254+
'eid' => $this->faker->regexify(config('app.EID_REGEX')),
255+
'authors' => $authors,
256+
'authors_formatted' => [
257+
'APA' => Publication::formatAuthorsApa($authors),
258+
],
259+
'status' => 'published',
260+
'visibility' => true,
261+
'citation-type' => $this->faker->optional(0.5)->word(),
262+
'citation-value' => $this->faker->optional(0.5)->word(),
263+
],
264+
];
265+
});
266+
}
267+
268+
/**
269+
* Return array of authors names formatted in any of the $this->$authors_names_patterns formats
270+
*/
271+
public function authorsNames() {
272+
$names = [];
273+
274+
for ($i = 0; $i < $this->authors_count; $i++) {
275+
$elem = $this->faker->randomElement(array_keys($this->authors_names_patterns));
276+
$names[] = eval($this->authors_names_patterns[$elem]);
277+
}
278+
279+
return $names;
280+
}
281+
183282
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use App\Profile;
6+
use App\ProfileData;
7+
use Tests\TestCase;
8+
use App\Helpers\Publication;
9+
use Illuminate\Foundation\Testing\RefreshDatabase;
10+
use Tests\Feature\Traits\LoginWithRole;
11+
use Illuminate\Foundation\Testing\WithFaker;
12+
use Tests\Feature\Traits\MockPublicationsRepository;
13+
use App\Repositories\OrcidPublicationsRepository;
14+
use Tests\Feature\Traits\HasJson;
15+
16+
class PublicationsRepositoryTest extends TestCase
17+
{
18+
use MockPublicationsRepository;
19+
use RefreshDatabase;
20+
use WithFaker;
21+
use LoginWithRole;
22+
use HasJson;
23+
24+
/**
25+
* Indicates whether the default seeder should run before each test.
26+
*
27+
* @var bool
28+
*/
29+
protected $seed = true;
30+
public array $existing_publications_data;
31+
public Profile $profile;
32+
33+
/** @test
34+
** @group orcid_import
35+
**/
36+
public function testImportOrcidPublications()
37+
{
38+
$this->profile = Profile::factory()
39+
->hasData([
40+
'data->orc_id_managed' => 1,
41+
'data->orc_id' => $this->faker()->numerify(),
42+
])
43+
->has(
44+
ProfileData::factory() //count = 3
45+
->existing_publication('general'),
46+
'data')
47+
->has(
48+
ProfileData::factory()
49+
->count(2)
50+
->state([
51+
'type' => 'publications',
52+
'data->sort_order' => $this->faker->year()
53+
])
54+
->general(), 'data')
55+
->create();
56+
57+
$this->assertTrue($this->profile->hasOrcidManagedPublications());
58+
59+
$this->assertCount(5, $this->profile->publications);
60+
$this->assertDatabaseCount('profile_data', 6);
61+
62+
// $this->output("PROFILE PUBLICATIONS CREATED", $this->profile->publications, ['profile_id', 'sort_order', 'title']);
63+
64+
$publications_edit_route = route('profiles.edit', [
65+
'profile' => $this->profile,
66+
'section' => 'publications',
67+
]);
68+
69+
$orcid_pubs_repo = $this->mockPublicationsRepository();
70+
71+
$this->instance(OrcidPublicationsRepository::class, $orcid_pubs_repo);
72+
$this->loginAsAdmin();
73+
74+
$this->followingRedirects()
75+
->get($publications_edit_route)
76+
->assertStatus(200)
77+
->assertViewIs('profiles.show')
78+
->assertSee('Publications updated via ORCID.');
79+
80+
$this->profile->refresh();
81+
$this->assertCount(9, $this->profile->publications);
82+
$this->assertDatabaseCount('profile_data', 10);
83+
84+
foreach ($this->profile->publications as $orcid_pub) {
85+
$this->assertDatabaseHas(
86+
'profile_data',
87+
['data' => $this->castToJson((array)$orcid_pub->data)]
88+
);
89+
90+
if (isset($orcid_pub->data['authors'])) {
91+
92+
$authors = Publication::formatAuthorsNames($orcid_pub->data['authors']);
93+
94+
foreach ($authors as $author) {
95+
$this->assertMatchesRegularExpression(Publication::REGEX_PATTERNS['APA'], $author['APA']);
96+
97+
}
98+
}
99+
}
100+
}
101+
102+
/**
103+
* Output a message to the console and log file
104+
*/
105+
public function output(string $message, $items = null, $attributes = null ): void
106+
{
107+
echo "\n $message \n";
108+
109+
if (!is_null($items)) {
110+
111+
foreach ($items as $key => $item) {
112+
$string = "$key ";
113+
foreach ($attributes as $attr) {
114+
$string .= $item->$attr . " ";
115+
}
116+
$string .= "\n";
117+
echo $string;
118+
}
119+
}
120+
}
121+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Tests\Feature\Traits;
4+
5+
use App\ProfileData;
6+
use App\Repositories\OrcidPublicationsRepository;
7+
8+
trait MockPublicationsRepository
9+
{
10+
/**
11+
* Partial mock to return the orcid API response containing publications (ProfileData collection)
12+
*
13+
* @return OrcidPublicationsRepository
14+
*/
15+
public function mockPublicationsRepository()
16+
{
17+
$publications = $this->makePublications();
18+
19+
// $this->output("API PUBLICATIONS TO SYNC", $publications, ['profile_id', 'sort_order', 'title']);
20+
21+
$pubs_mock = mock(OrcidPublicationsRepository::class)->makePartial();
22+
23+
$pubs_mock
24+
->shouldReceive('getCachedPublications')
25+
->andReturn($publications);
26+
27+
return $pubs_mock;
28+
}
29+
30+
/**
31+
* Returns a ProfileDataFactory collection of publications exisisting in the DB and new publications
32+
*
33+
* @return \Illuminate\Support\Collection<ProfileDataFactory>
34+
*/
35+
public function makePublications()
36+
{
37+
$orcid_api_new_pubs =
38+
ProfileData::factory()
39+
->count(4)
40+
->orcid_publication($this->profile)
41+
->make();
42+
43+
$orcid_api_existing_pubs =
44+
ProfileData::factory() //count = 3
45+
->existing_publication('orcid_publication', $this->profile)
46+
->make();
47+
48+
$orcid_api_new_pubs->map(fn($pub) => $orcid_api_existing_pubs->push($pub));
49+
50+
return $orcid_api_existing_pubs;
51+
}
52+
53+
/**
54+
* Clean up the testing environment before the next test.
55+
*
56+
* @return void
57+
*/
58+
protected function tearDown(): void
59+
{
60+
// fix for the config() helper not resolving in tests using Mockery
61+
$config = app('config');
62+
parent::tearDown();
63+
app()->instance('config', $config);
64+
}
65+
}

0 commit comments

Comments
 (0)