Skip to content

Commit

Permalink
✅ Adds Orcid data import optimization feature tests
Browse files Browse the repository at this point in the history
Includes ProfileDataFactory for publications and authors, publications mock and feature test.
  • Loading branch information
betsyecastro committed Apr 26, 2024
1 parent cacd441 commit 628400d
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 3 deletions.
7 changes: 4 additions & 3 deletions app/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ public function hasOrcidManagedPublications()

public function updateORCID()
{
$publicationsManager = new OrcidPublicationsRepository($this);

return $publicationsManager->syncPublications();
$publications_manager = app()->make(OrcidPublicationsRepository::class);
$publications_manager->setProfile($this);

return $publications_manager->syncPublications();
}


Expand Down
4 changes: 4 additions & 0 deletions app/Providers/RepositoryServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Providers;

use App\Profile;
use App\Repositories\Contracts\PublicationsRepositoryContract;
use App\Repositories\OrcidPublicationsRepository;
use Illuminate\Support\ServiceProvider;
Expand All @@ -25,6 +26,9 @@ public function boot()
*/
public function register()
{
$this->app->bind(OrcidPublicationsRepository::class, function($app) {
return new OrcidPublicationsRepository($app[Profile::class]);
});
$this->app->bind(PublicationsRepositoryContract::class, OrcidPublicationsRepository::class);
}
}
99 changes: 99 additions & 0 deletions database/factories/ProfileDataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Profile;
use App\ProfileData;
use App\Helpers\Publication;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;

Expand All @@ -29,6 +30,33 @@ class ProfileDataFactory extends Factory
'Library',
];

protected $existing_publications_data =
[
0 => [
'sort_order' => '2021',
'title' => 'Existing Publication Title #1',
],
1 => [
'sort_order' => '2022',
'title' => 'Existing Publication Title #2',
],
2 => [
'sort_order' => '2023',
'title' => 'Existing Publication Title #3',
],
];

protected $authors_names_patterns =
[
'return ($this->faker->unique()->lastName() . ", " . strtoupper($this->faker->randomLetter()) . ". " . strtoupper($this->faker->randomLetter()) . ".");',
'return ($this->faker->unique()->lastName() . ", " . strtoupper($this->faker->randomLetter()). ".");',
'return ($this->faker->firstName() . " " . $this->faker->lastName());',
'return ($this->faker->lastName() . " " . strtoupper($this->faker->randomLetter()) . strtoupper($this->faker->randomLetter()));',
'return ($this->faker->lastName() . " " . strtoupper($this->faker->randomLetter()));',
];

public int $authors_count = 5;

/**
* Define the model's default state.
*
Expand Down Expand Up @@ -180,4 +208,75 @@ public function news()
];
});
}

/**
* Data Type "publications" with pre-defined sort_order and title
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function existing_publication($type, $profile = null)
{
return $this
->count(3)
->state(['type' => 'publications'])
->$type($profile)
->sequence(function($sequence) {
return [
'sort_order' => $this->existing_publications_data[$sequence->index]['sort_order'],
'data->title' => $this->existing_publications_data[$sequence->index]['title'],
];
});
}

/**
* Data Type "publications" sourced from the Orcid API. Formatted, and ready to sync with ProfileData
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function orcid_publication(Profile $profile = null) {
return $this->state(function (array $attributes) use ($profile) {

$authors = $this->authorsNames();

return [
'profile_id' => $profile->id,
'sort_order' => $this->faker->year(),
'data' => [
'put-code' => $this->faker->numberBetween(99000,100000),
'url' => $this->faker->url(),
'title' => $this->faker->sentence(),
'year' => $this->faker->year(),
'type' => 'Journal',
'month' => $this->faker->month(),
'day' => $this->faker->dayOfMonth(),
'journal_title' => $this->faker->sentence(),
'doi' => $this->faker->regexify(config('app.DOI_REGEX')),
'eid' => $this->faker->regexify(config('app.EID_REGEX')),
'authors' => $authors,
'authors_formatted' => [
'APA' => Publication::formatAuthorsApa($authors),
],
'status' => 'published',
'visibility' => true,
'citation-type' => $this->faker->optional(0.5)->word(),
'citation-value' => $this->faker->optional(0.5)->word(),
],
];
});
}

/**
* Return array of authors names formatted in any of the $this->$authors_names_patterns formats
*/
public function authorsNames() {
$names = [];

for ($i = 0; $i < $this->authors_count; $i++) {
$elem = $this->faker->randomElement(array_keys($this->authors_names_patterns));
$names[] = eval($this->authors_names_patterns[$elem]);
}

return $names;
}

}
121 changes: 121 additions & 0 deletions tests/Feature/PublicationsRepositoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Tests\Feature;

use App\Profile;
use App\ProfileData;
use Tests\TestCase;
use App\Helpers\Publication;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\Feature\Traits\LoginWithRole;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\Feature\Traits\MockPublicationsRepository;
use App\Repositories\OrcidPublicationsRepository;
use Tests\Feature\Traits\HasJson;

class PublicationsRepositoryTest extends TestCase
{
use MockPublicationsRepository;
use RefreshDatabase;
use WithFaker;
use LoginWithRole;
use HasJson;

/**
* Indicates whether the default seeder should run before each test.
*
* @var bool
*/
protected $seed = true;
public array $existing_publications_data;
public Profile $profile;

/** @test
** @group orcid_import
**/
public function testImportOrcidPublications()
{
$this->profile = Profile::factory()
->hasData([
'data->orc_id_managed' => 1,
'data->orc_id' => $this->faker()->numerify(),
])
->has(
ProfileData::factory() //count = 3
->existing_publication('general'),
'data')
->has(
ProfileData::factory()
->count(2)
->state([
'type' => 'publications',
'data->sort_order' => $this->faker->year()
])
->general(), 'data')
->create();

$this->assertTrue($this->profile->hasOrcidManagedPublications());

$this->assertCount(5, $this->profile->publications);
$this->assertDatabaseCount('profile_data', 6);

// $this->output("PROFILE PUBLICATIONS CREATED", $this->profile->publications, ['profile_id', 'sort_order', 'title']);

$publications_edit_route = route('profiles.edit', [
'profile' => $this->profile,
'section' => 'publications',
]);

$orcid_pubs_repo = $this->mockPublicationsRepository();

$this->instance(OrcidPublicationsRepository::class, $orcid_pubs_repo);
$this->loginAsAdmin();

$this->followingRedirects()
->get($publications_edit_route)
->assertStatus(200)
->assertViewIs('profiles.show')
->assertSee('Publications updated via ORCID.');

$this->profile->refresh();
$this->assertCount(9, $this->profile->publications);
$this->assertDatabaseCount('profile_data', 10);

foreach ($this->profile->publications as $orcid_pub) {
$this->assertDatabaseHas(
'profile_data',
['data' => $this->castToJson((array)$orcid_pub->data)]
);

if (isset($orcid_pub->data['authors'])) {

$authors = Publication::formatAuthorsNames($orcid_pub->data['authors']);

foreach ($authors as $author) {
$this->assertMatchesRegularExpression(Publication::REGEX_PATTERNS['APA'], $author['APA']);

}
}
}
}

/**
* Output a message to the console and log file
*/
public function output(string $message, $items = null, $attributes = null ): void
{
echo "\n $message \n";

if (!is_null($items)) {

foreach ($items as $key => $item) {
$string = "$key ";
foreach ($attributes as $attr) {
$string .= $item->$attr . " ";
}
$string .= "\n";
echo $string;
}
}
}
}
65 changes: 65 additions & 0 deletions tests/Feature/Traits/MockPublicationsRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Tests\Feature\Traits;

use App\ProfileData;
use App\Repositories\OrcidPublicationsRepository;

trait MockPublicationsRepository
{
/**
* Partial mock to return the orcid API response containing publications (ProfileData collection)
*
* @return OrcidPublicationsRepository
*/
public function mockPublicationsRepository()
{
$publications = $this->makePublications();

// $this->output("API PUBLICATIONS TO SYNC", $publications, ['profile_id', 'sort_order', 'title']);

$pubs_mock = mock(OrcidPublicationsRepository::class)->makePartial();

$pubs_mock
->shouldReceive('getCachedPublications')
->andReturn($publications);

return $pubs_mock;
}

/**
* Returns a ProfileDataFactory collection of publications exisisting in the DB and new publications
*
* @return \Illuminate\Support\Collection<ProfileDataFactory>
*/
public function makePublications()
{
$orcid_api_new_pubs =
ProfileData::factory()
->count(4)
->orcid_publication($this->profile)
->make();

$orcid_api_existing_pubs =
ProfileData::factory() //count = 3
->existing_publication('orcid_publication', $this->profile)
->make();

$orcid_api_new_pubs->map(fn($pub) => $orcid_api_existing_pubs->push($pub));

return $orcid_api_existing_pubs;
}

/**
* Clean up the testing environment before the next test.
*
* @return void
*/
protected function tearDown(): void
{
// fix for the config() helper not resolving in tests using Mockery
$config = app('config');
parent::tearDown();
app()->instance('config', $config);
}
}

0 comments on commit 628400d

Please sign in to comment.