Components Assets view + stubbed API tests (#3325)

* Toggles the disabled state of auto_increment_prefix

To insert a prefix you had to toggle the checkbox, save the settings and reload. With this script it is immediate. Fixes #1390

* Delete asset image: made checkbox more visible

Related to #3153

* Added personal-access-token component

* Created basic API testing configuration

* First version of /components endpoind cest

* On-the-fly bearer token generation

* Completed testing of PATCH and PUT methods

* Added /components/{id}/assets route with tests

* Updated route and dataTable in view

* Completed test assertion

* Added links to assets in ComponentsAssets view

* Linked Company in AssetView page
This commit is contained in:
Andrea Bergamasco 2017-02-21 23:26:46 +01:00 committed by snipe
parent 4f73a13c6b
commit fde46251de
12 changed files with 401 additions and 3 deletions

View file

@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\ComponentsTransformer;
use App\Http\Transformers\ComponentsAssetsTransformer;
use App\Models\Component;
use App\Models\Company;
use App\Helpers\Helper;
@ -131,4 +133,26 @@ class ComponentsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.delete.success')));
}
/**
* Display all assets attached to a component
*
* @author [A. Bergamasco] [@vjandrea]
* @since [v4.0]
* @param Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function getAssets(Request $request, $id)
{
$this->authorize('index', Asset::class);
$component = Component::findOrFail($id);
$assets = $component->assets();
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
return (new ComponentsAssetsTransformer)->transformAssets($assets, $total);
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Http\Transformers;
use App\Models\Asset;
use Illuminate\Database\Eloquent\Collection;
use App\Http\Transformers\UsersTransformer;
use Gate;
class ComponentsAssetsTransformer
{
public function transformAssets (Collection $assets, $total)
{
$array = array();
foreach ($assets as $asset) {
$array[] = self::transformAsset($asset);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformAsset (Asset $asset)
{
$array = [
'id' => $asset->id,
'name' => e($asset->name),
'created_at' => $asset->created_at->format('Y-m-d'),
'qty' => $asset->components()->count(),
'can_checkout' => $asset->availableForCheckout(),
];
$permissions_array['available_actions'] = [
'checkout' => Gate::allows('checkout', Asset::class) ? true : false,
'checkin' => Gate::allows('checkin', Asset::class) ? true : false,
'update' => Gate::allows('update', Asset::class) ? true : false,
'delete' => Gate::allows('delete', Asset::class) ? true : false,
];
$array += $permissions_array;
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
$fields_array = [$field->name => $asset->{$field->convertUnicodeDbSlug()}];
$array += $fields_array;
}
}
return $array;
}
public function transformAssetsDatatable ($assets) {
return (new DatatablesTransformer)->transformDatatables($assets);
}
}

View file

@ -49,14 +49,14 @@
name="component_users"
class="table table-striped snipe-table"
id="table"
data-url="{{route('api.components.show', $component->id)}}"
data-url="{{route('api.components.assets', $component->id)}}"
data-cookie="true"
data-click-to-select="true"
data-cookie-id-table="componentDetailTable-{{ config('version.hash_version') }}"
>
<thead>
<tr>
<th data-switchable="false" data-searchable="false" data-sortable="false" data-field="name">{{ trans('general.asset') }}</th>
<th data-switchable="false" data-searchable="false" data-sortable="false" data-field="name" data-formatter="hardwareLinkFormatter">{{ trans('general.asset') }}</th>
<th data-switchable="false" data-searchable="false" data-sortable="false" data-field="qty">{{ trans('general.qty') }}</th>
<th data-switchable="false" data-searchable="false" data-sortable="false" data-field="created_at">{{ trans('general.date') }}</th>
</tr>

View file

@ -84,7 +84,7 @@
@if ($asset->company)
<tr>
<td>{{ trans('general.company') }}</td>
<td>{{ $asset->company->name }}</td>
<td><a href="{{ url('/companies/' . $asset->company->id) }}">{{ $asset->company->name }}</a></td>
</tr>
@endif

View file

@ -11,6 +11,9 @@
@if (!config('app.lock_passwords'))
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
@if(env('APP_ENV') != 'production')
<passport-personal-access-tokens></passport-personal-access-tokens>
@endif
@else
<p class="help-block">{{ trans('general.feature_disabled') }}</p>
@endif

View file

@ -142,6 +142,11 @@ Route::group(['prefix' => 'v1','namespace' => 'Api'], function () {
]
);
Route::get('components/{id}/assets', [
'as' =>'api.components.assets',
'uses' => 'ComponentsController@getAssets',
]);
Route::resource('suppliers', 'SuppliersController',
['names' =>

View file

@ -0,0 +1,46 @@
<?php
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
*
* @SuppressWarnings(PHPMD)
*/
class ApiTester extends \Codeception\Actor
{
use _generated\ApiTesterActions;
/**
* Define custom actions here
*/
public function getToken(\App\Models\User $user)
{
$client_repository = new \Laravel\Passport\ClientRepository();
$client = $client_repository->createPersonalAccessClient($user->id, 'Codeception API Test Client',
'http://localhost/');
\Illuminate\Support\Facades\DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => new DateTime,
'updated_at' => new DateTime,
]);
$user->permissions = json_encode(['superuser' => true]);
$user->save();
$token = $user->createToken('CodeceptionAPItestToken')->accessToken;
return $token;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Helper;
// here you can define custom actions
// all public methods declared in helper class will be available in $I
class Api extends \Codeception\Module
{
}

13
tests/api.suite.yml Normal file
View file

@ -0,0 +1,13 @@
class_name: ApiTester
modules:
enabled:
- \Helper\Api
- REST:
url: /api/v1
depends: Laravel5
- Asserts
config:
- Laravel5:
environment_file: .env.testing
disable_middleware: true
cleanup: true

View file

@ -0,0 +1,59 @@
<?php
class ApiComponentsAssetsCest
{
protected $faker;
protected $user;
public function _before(ApiTester $I)
{
$this->faker = \Faker\Factory::create();
$this->user = \App\Models\User::find(1);
$I->amBearerAuthenticated($I->getToken($this->user));
}
/** @test */
public function indexComponentsAssets(ApiTester $I)
{
$I->wantTo('Get a list of assets related to a component');
// generate
$component = factory(\App\Models\Component::class, 'component')
->create(['user_id' => $this->user->id, 'qty' => 20]);
$assets = factory(\App\Models\Asset::class, 'asset', 2)
->create(['user_id' => $this->user->id])
->each(function ($asset) use ($component) {
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'user_id' => $this->user->id,
'created_at' => date('Y-m-d H:i:s'),
'assigned_qty' => 2,
'asset_id' => $asset->id
]);
});
$I->sendGET('/components/' . $component->id . '/assets/');
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$response = json_decode($I->grabResponse());
$I->assertEquals(2, $response->total);
$I->assertInstanceOf(\Illuminate\Database\Eloquent\Collection::class, $assets);
$I->seeResponseContainsJson(['rows' => [
0 => [
'name' => $assets[0]->name,
'id' => $assets[0]->id,
'created_at' => $assets[0]->created_at->format('Y-m-d'),
],
1 => [
'name' => $assets[1]->name,
'id' => $assets[1]->id,
'created_at' => $assets[1]->created_at->format('Y-m-d'),
],
]
]);
}
}

View file

@ -0,0 +1,182 @@
<?php
use Illuminate\Support\Facades\Auth;
class ApiComponentsCest
{
protected $faker;
protected $user;
public function _before(ApiTester $I)
{
$this->faker = \Faker\Factory::create();
$this->user = \App\Models\User::find(1);
$I->amBearerAuthenticated($I->getToken($this->user));
}
/** @test */
public function indexComponents(ApiTester $I)
{
$I->wantTo('Get a list of components');
// setup
$components = factory(\App\Models\Component::class, 'component', 10)->create(['user_id' => $this->user->id]);
// call
$I->sendGET('/components');
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
// sample verify
$component = $components->random();
$I->seeResponseContainsJson([
'name' => $component->name,
'qty' => $component->qty,
]);
}
/** @test */
public function createComponent(ApiTester $I)
{
$I->wantTo('Create a new component');
// setup
$category = factory(\App\Models\Category::class, 'category')->create(['user_id' => $this->user->id]);
$location = factory(\App\Models\Location::class, 'location')->create(['user_id' => $this->user->id]);
$company = factory(\App\Models\Company::class, 'company')->create();
$data = [
'category_id' => $category->id,
'company_id' => $company->id,
'location_id' => $location->id,
'name' => $this->faker->sentence(3),
'purchase_cost' => $this->faker->randomFloat(2, 0),
'purchase_date' => $this->faker->dateTime->format('Y-m-d'),
'qty' => rand(1, 10),
];
// create
$I->sendPOST('/components', $data);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$response = json_decode($I->grabResponse());
$id = $response->payload->id;
$I->assertEquals('success', $response->status);
// verify
$I->sendGET('/components/' . $id);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$I->seeResponseContainsJson([
'id' => $id,
'category' => [
'id' => $data['category_id'],
'name' => $category->name,
],
'company' => [
'id' => $data['company_id'],
'name' => $company->name,
],
'location' => [
'id' => $data['location_id'],
'name' => $location->name,
],
'name' => $data['name'],
'qty' => $data['qty'],
'purchase_cost' => $data['purchase_cost'],
'purchase_date' => $data['purchase_date'],
]);
}
/** @test */
public function updateComponentWithPatch(ApiTester $I)
{
$I->wantTo('Update a component with PATCH');
// create
$component = factory(\App\Models\Component::class, 'component')->create();
$I->assertInstanceOf(\App\Models\Component::class, $component);
$data = [
'name' => $this->faker->sentence(3),
'qty' => $this->faker->randomDigit + 1,
];
$I->assertNotEquals($component->name, $data['name']);
// update
$I->sendPATCH('/components/' . $component->id, $data);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$response = json_decode($I->grabResponse());
$I->assertEquals('success', $response->status);
// verify
$I->sendGET('/components/' . $component->id);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$I->seeResponseContainsJson([
'name' => $data['name'],
'id' => $component->id,
'qty' => $data['qty'],
]);
}
/** @test */
public function updateComponentWithPut(ApiTester $I)
{
$I->wantTo('Update a component with PUT');
// create
$component = factory(\App\Models\Component::class, 'component')->create();
$I->assertInstanceOf(\App\Models\Component::class, $component);
$data = [
'name' => $this->faker->sentence(3),
];
$I->assertNotEquals($component->name, $data['name']);
// update
$I->sendPUT('/components/' . $component->id, $data);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$response = json_decode($I->grabResponse());
$I->assertEquals('success', $response->status);
// verify
$I->sendGET('/components/' . $component->id);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
$I->seeResponseContainsJson([
'name' => e($data['name']),
'id' => e($component->id),
'qty' => e($component->qty),
]);
}
/** @test */
public function deleteComponentTest(ApiTester $I)
{
$I->wantTo('Delete a component');
// create
$component = factory(\App\Models\Component::class, 'component')->create();
$I->assertInstanceOf(\App\Models\Component::class, $component);
// delete
$I->sendDELETE('/components/' . $component->id);
$I->seeResponseIsJson();
$I->seeResponseCodeIs(200);
// verify, expect a 404
$I->sendGET('/components/' . $component->id);
$I->seeResponseCodeIs(404);
// $I->seeResponseIsJson(); // @todo: response is not JSON
}
}

2
tests/api/_bootstrap.php Normal file
View file

@ -0,0 +1,2 @@
<?php
// Here you can initialize variables that will be available to your tests