mirror of
https://github.com/snipe/snipe-it.git
synced 2025-01-11 22:07:29 -08:00
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:
parent
4f73a13c6b
commit
fde46251de
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
54
app/Http/Transformers/ComponentsAssetsTransformer.php
Normal file
54
app/Http/Transformers/ComponentsAssetsTransformer.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' =>
|
||||
|
|
46
tests/_support/ApiTester.php
Normal file
46
tests/_support/ApiTester.php
Normal 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;
|
||||
}
|
||||
}
|
10
tests/_support/Helper/Api.php
Normal file
10
tests/_support/Helper/Api.php
Normal 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
13
tests/api.suite.yml
Normal 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
|
59
tests/api/ApiComponentsAssetsCest.php
Normal file
59
tests/api/ApiComponentsAssetsCest.php
Normal 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'),
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
182
tests/api/ApiComponentsCest.php
Normal file
182
tests/api/ApiComponentsCest.php
Normal 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
2
tests/api/_bootstrap.php
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
// Here you can initialize variables that will be available to your tests
|
Loading…
Reference in a new issue