1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-13 22:54:25 +01:00

Test for API

This commit is contained in:
David Bomba 2019-03-27 15:50:13 +11:00
parent e8da725aa1
commit ebddf93353
20 changed files with 452 additions and 40 deletions

View File

@ -47,3 +47,4 @@ MULTI_DB_ENABLED=true
POSTMARK_API_TOKEN= POSTMARK_API_TOKEN=
GOOGLE_MAPS_API_KEY= GOOGLE_MAPS_API_KEY=
API_SECRET=superdoopersecrethere

View File

@ -3,7 +3,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\DataMapper\ClientSettings; use App\DataMapper\ClientSettings;
use App\Datatables\MakesActionMenu;
use App\Factory\ClientFactory; use App\Factory\ClientFactory;
use App\Http\Requests\Client\CreateClientRequest; use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\EditClientRequest; use App\Http\Requests\Client\EditClientRequest;
@ -20,7 +19,6 @@ use App\Models\Currency;
use App\Models\Size; use App\Models\Size;
use App\Repositories\ClientRepository; use App\Repositories\ClientRepository;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesMenu;
use App\Utils\Traits\UserSessionAttributes; use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -34,8 +32,6 @@ class ClientController extends Controller
{ {
use UserSessionAttributes; use UserSessionAttributes;
use MakesHash; use MakesHash;
use MakesMenu;
use MakesActionMenu;
/** /**
* @var ClientRepository * @var ClientRepository
@ -70,17 +66,16 @@ class ClientController extends Controller
public function show(ShowClientRequest $request, Client $client) public function show(ShowClientRequest $request, Client $client)
{ {
$data = [ $data = [
'client' => $client, 'client' => $client,
'company' => auth()->user()->company(), 'company' => $client->company(),
'meta' => collect([ 'meta' => collect([
'google_maps_api_key' => config('ninja.google_maps_api_key') 'google_maps_api_key' => config('ninja.google_maps_api_key')
]) ])
]; ];
return response()->json($data); //return response()->json($data);
return redirect()->route('clients.edit', ['id' => $this->encodePrimarykey($client->id)]);
} }
/** /**
@ -94,9 +89,9 @@ class ClientController extends Controller
$data = [ $data = [
'client' => $client, 'client' => $client,
'settings' => collect(ClientSettings::buildClientSettings(auth()->user()->company()->settings_object, $client->client_settings_object)), 'settings' => collect(ClientSettings::buildClientSettings($client->company()->settings_object, $client->client_settings_object)),
'hashed_id' => $this->encodePrimarykey($client->id), 'hashed_id' => $this->encodePrimarykey($client->id),
'company' => auth()->user()->company(), 'company' => $client->company(),
'sizes' => Size::all(), 'sizes' => Size::all(),
]; ];

View File

@ -70,6 +70,8 @@ class Kernel extends HttpKernel
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'query_logging' => \App\Http\Middleware\QueryLogging::class, 'query_logging' => \App\Http\Middleware\QueryLogging::class,
'token_auth' => \App\Http\Middleware\TokenAuth::class,
'api_secret_check' => \App\Http\Middleware\ApiSecretCheck::class,
]; ];
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use App\Models\User;
use Closure;
use Illuminate\Support\Facades\Log;
class ApiSecretCheck
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if( $request->header('X-API-SECRET') && ($request->header('X-API-SECRET') == config('ninja.api_secret')) )
{
return $next($request);
}
else {
$error['error'] = ['message' => 'Invalid secret'];
return response()->json(json_encode($error, JSON_PRETTY_PRINT) ,403);
}
}
}

View File

@ -18,8 +18,8 @@ class TokenAuth
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if($request->header('X-API-TOKEN') if( $request->header('X-API-TOKEN') && ($user = CompanyToken::whereRaw("BINARY `token`= ?",[$request->header('X-API-TOKEN')])->first()->user ) )
&& ($user = CompanyToken::whereRaw("BINARY `token`= ?",[$request->header('X-API-TOKEN')])->user)) { {
auth()->login($user); auth()->login($user);

View File

@ -50,7 +50,7 @@ class Account extends BaseModel
public function default_company() public function default_company()
{ {
return $this->hasOne(Company::class, 'default_company_id', 'id'); return $this->hasOne(Company::class, 'id', 'default_company_id');
} }
/** /**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo

View File

@ -60,6 +60,16 @@ class User extends Authenticatable implements MustVerifyEmail
'slack_webhook_url', 'slack_webhook_url',
]; ];
public function token()
{
return $this->tokens->first();
}
public function tokens()
{
return $this->hasMany(CompanyToken::class)->orderBy('id');
}
/** /**
* Returns all companies a user has access to. * Returns all companies a user has access to.
* *

View File

@ -119,9 +119,12 @@ class RouteServiceProvider extends ServiceProvider
*/ */
protected function mapApiRoutes() protected function mapApiRoutes()
{ {
Route::prefix('api') Route::prefix('api/v1')
->middleware('api') ->middleware('api')
->namespace($this->namespace) ->namespace($this->namespace)
->group(base_path('routes/api.php')); ->group(base_path('routes/api.php'));
} }
} }

View File

@ -0,0 +1,54 @@
<?php
namespace App\Ninja\Transformers;
use App\Models\Account;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
/**
* Class AccountTransformer.
*/
class AccountTransformer extends EntityTransformer
{
trait MakesHash;
/**
* @SWG\Property(property="account_key", type="string", example="123456")
*/
/**
* @var array
*/
protected $defaultIncludes = [
];
/**
* @var array
*/
protected $availableIncludes = [
'default_company',
];
/**
* @param Account $account
*
* @throws \Laracasts\Presenter\Exceptions\PresenterException
*
* @return array
*/
public function transform(Account $account)
{
return [
'id' => $this->encodePrimaryKey($account->id),
];
}
public function includeDefaultCompany(Account $account)
{
$transformer = new CompanyTransformer($this->serializer);
return $this->includeItem($account->default_company, $transformer, Company::class);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Ninja\Transformers;
use App\Utils\Traits\MakesHash;
/**
* Class AccountTransformer.
*/
class CompanyTransformer extends EntityTransformer
{
trait MakesHash;
/**
* @SWG\Property(property="account_key", type="string", example="123456")
*/
/**
* @var array
*/
protected $defaultIncludes = [
];
/**
* @var array
*/
protected $availableIncludes = [
'users',
'account',
'clients',
'contacts',
'invoices',
'tax_rates',
'products',
'country',
'timezone',
'language',
'expenses',
'payments',
];
/**
* @param Company $company
*
* @return array
*/
public function transform(Company $company)
{
return [
'id' => $this->encodePrimaryKey($company->id),
'name' => $company->name,
'company_key' => $company->company_key,
'last_login' => $company->last_login,
'address1' => $company->address1,
'address2' => $company->address2,
'city' => $company->city,
'state' => $company->state,
'postal_code' => $company->postal_code,
'work_phone' => $company->work_phone,
'work_email' => $company->work_email,
'country_id' => (int) $company->country_id,
'subdomain' => $company->subdomain,
'db' => $company->db,
'vat_number' => $company->vat_number,
'id_number' => $company->id_number,
'size_id' => (int) $company->size_id,
'industry_id' => (int) $company->industry_id,
'settings' => $company->settings,
'updated_at' => $user->updated_at,
'deleted_at' => $user->deleted_at,
];
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Ninja\Transformers;
use App\Models\Account;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Payment;
/**
* @SWG\Definition(definition="Payment", required={"invoice_id"}, @SWG\Xml(name="Payment"))
*/
class PaymentTransformer extends EntityTransformer
{
/**
* @SWG\Property(property="id", type="integer", example=1, readOnly=true)
* @SWG\Property(property="amount", type="number", format="float", example=10, readOnly=true)
* @SWG\Property(property="transaction_reference", type="string", example="Transaction Reference")
* @SWG\Property(property="payment_date", type="string", format="date", example="2018-01-01")
* @SWG\Property(property="updated_at", type="integer", example=1451160233, readOnly=true)
* @SWG\Property(property="archived_at", type="integer", example=1451160233, readOnly=true)
* @SWG\Property(property="is_deleted", type="boolean", example=false, readOnly=true)
* @SWG\Property(property="invoice_id", type="integer", example=1)
* @SWG\Property(property="invoice_number", type="string", example="Invoice Number")
* @SWG\Property(property="private_notes", type="string", example="Notes")
* @SWG\Property(property="exchange_rate", type="number", format="float", example=10)
* @SWG\Property(property="exchange_currency_id", type="integer", example=1)
*/
protected $defaultIncludes = [];
protected $availableIncludes = [
'client',
'invoice',
];
public function __construct($account = null, $serializer = null, $invoice = null)
{
parent::__construct($account, $serializer);
$this->invoice = $invoice;
}
public function includeInvoice(Payment $payment)
{
$transformer = new InvoiceTransformer($this->account, $this->serializer);
return $this->includeItem($payment->invoice, $transformer, 'invoice');
}
public function includeClient(Payment $payment)
{
$transformer = new ClientTransformer($this->account, $this->serializer);
return $this->includeItem($payment->client, $transformer, 'client');
}
public function transform(Payment $payment)
{
return array_merge($this->getDefaults($payment), [
'id' => (int) $payment->public_id,
'amount' => (float) $payment->amount,
'transaction_reference' => $payment->transaction_reference ?: '',
'payment_date' => $payment->payment_date ?: '',
'updated_at' => $this->getTimestamp($payment->updated_at),
'archived_at' => $this->getTimestamp($payment->deleted_at),
'is_deleted' => (bool) $payment->is_deleted,
'payment_type_id' => (int) ($payment->payment_type_id ?: 0),
'invoice_id' => (int) ($this->invoice ? $this->invoice->public_id : $payment->invoice->public_id),
'invoice_number' => $this->invoice ? $this->invoice->invoice_number : $payment->invoice->invoice_number,
'private_notes' => $payment->private_notes ?: '',
'exchange_rate' => (float) $payment->exchange_rate,
'exchange_currency_id' => (int) $payment->exchange_currency_id,
'refunded' => (float) $payment->refunded,
'payment_status_id' => (int) ($payment->payment_status_id ?: PAYMENT_STATUS_COMPLETED),
]);
}
}

View File

@ -9,6 +9,7 @@ return [
'app_version' => '0.1', 'app_version' => '0.1',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'app_env' => env('APP_ENV', 'development'), 'app_env' => env('APP_ENV', 'development'),
'api_secret' => env('API_SECRET', ''),
'google_maps_api_key' => env('GOOGLE_MAPS_API_KEY'), 'google_maps_api_key' => env('GOOGLE_MAPS_API_KEY'),
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller' 'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'

View File

@ -3,6 +3,7 @@
use App\Models\Account; use App\Models\Account;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\CompanyToken;
use App\Models\User; use App\Models\User;
use App\Models\UserAccount; use App\Models\UserAccount;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -38,6 +39,14 @@ class RandomDataSeeder extends Seeder
'confirmation_code' => $this->createDbHash(config('database.default')) 'confirmation_code' => $this->createDbHash(config('database.default'))
]); ]);
$company_token = CompanyToken::create([
'user_id' => $user->id,
'company_id' => $company->id,
'account_id' => $account->id,
'name' => 'test token',
'token' => str_random(64),
]);
$user->companies()->attach($company->id, [ $user->companies()->attach($company->id, [
'account_id' => $account->id, 'account_id' => $account->id,
'is_owner' => 1, 'is_owner' => 1,
@ -62,7 +71,7 @@ class RandomDataSeeder extends Seeder
]); ]);
factory(\App\Models\Client::class, 50)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){ factory(\App\Models\Client::class, 5)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
factory(\App\Models\ClientContact::class,1)->create([ factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $user->id, 'user_id' => $user->id,
@ -76,16 +85,7 @@ class RandomDataSeeder extends Seeder
'client_id' => $c->id, 'client_id' => $c->id,
'company_id' => $company->id 'company_id' => $company->id
]); ]);
/*
factory(\App\Models\ClientLocation::class,1)->create([
'client_id' => $c->id,
'is_primary_billing' => 1
]);
factory(\App\Models\ClientLocation::class,10)->create([
'client_id' => $c->id,
]);
*/
}); });

View File

@ -17,3 +17,52 @@ Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user(); return $request->user();
}); });
*/ */
Route::group(['middleware' => ['api_secret_check']], function () {
Route::post('signup', 'AccountController@store')->name('signup.submit');
});
Route::group(['middleware' => ['api_secret_check','token_auth']], function () {
Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
Route::resource('quotes', 'QuoteController'); // name = (quotes. index / create / show / update / destroy / edit
Route::post('quotes/bulk', 'QuoteController@bulk')->name('quotes.bulk');
Route::resource('recurring_invoices', 'RecurringInvoiceController'); // name = (recurring_invoices. index / create / show / update / destroy / edit
Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.bulk');
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit
Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit
Route::post('tasks/bulk', 'TaskController@bulk')->name('tasks.bulk');
Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit
Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk');
Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit
Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk');
Route::resource('user', 'UserProfileController'); // name = (clients. index / create / show / update / destroy / edit
Route::get('settings', 'SettingsController@index')->name('user.settings');
});

View File

@ -12,7 +12,6 @@ class CreateAccountTest extends DuskTestCase
{ {
use WithFaker; use WithFaker;
use DatabaseTransactions;
public function testSignupFormDisplayed() public function testSignupFormDisplayed()
@ -27,7 +26,6 @@ class CreateAccountTest extends DuskTestCase
*/ */
public function testCreateAValidUser() public function testCreateAValidUser()
{ {
DB::beginTransaction();
$this->browse(function (Browser $browser) { $this->browse(function (Browser $browser) {
$browser->visit('/signup') $browser->visit('/signup')
@ -41,7 +39,6 @@ class CreateAccountTest extends DuskTestCase
->assertPathIs('/dashboard'); ->assertPathIs('/dashboard');
}); });
DB::rollback();
} }
} }

View File

@ -0,0 +1,83 @@
<?php
namespace Tests\Feature;
use App\Jobs\Account\CreateAccount;
use App\Models\Account;
use App\Models\Client;
use App\Models\User;
use App\Utils\Traits\UserSessionAttributes;
use Faker\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Tests\TestCase;
class AccountTest extends TestCase
{
//use DatabaseTransactions;
public function setUp()
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
}
public function testAccountCreation()
{
$data = [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->unique()->safeEmail,
'password' => 'ALongAndBrilliantPassword123',
'_token' => csrf_token(),
'privacy_policy' => 1,
'terms_of_service' => 1
];
$response = $this->post('/signup', $data);
$response->assertStatus(200)
->assertJson([
'first_name' => $data['first_name'],
]);
}
public function testApiAccountCreation()
{
$data = [
'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName,
'email' => $this->faker->unique()->safeEmail,
'password' => 'ALongAndBrilliantPassword123',
'_token' => csrf_token(),
'privacy_policy' => 1,
'terms_of_service' => 1
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
])->post('/api/v1/signup', $data);
$response->assertStatus(200)
->assertJson([
'first_name' => $data['first_name'],
]);
}
}

View File

@ -2,11 +2,8 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Jobs\Account\CreateAccount;
use App\Models\Account; use App\Models\Account;
use App\Models\Client; use App\Models\Company;
use App\Models\User;
use App\Utils\Traits\UserSessionAttributes;
use Faker\Factory; use Faker\Factory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
@ -19,19 +16,22 @@ use Tests\TestCase;
class ClientTest extends TestCase class ClientTest extends TestCase
{ {
//use DatabaseTransactions;
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
Session::start(); Session::start();
$this->faker = \Faker\Factory::create(); $this->faker = \Faker\Factory::create();
Model::reguard(); Model::reguard();
} }
public function testAccountCreation() public function testClientList()
{ {
$data = [ $data = [
'first_name' => $this->faker->firstName, 'first_name' => $this->faker->firstName,
'last_name' => $this->faker->lastName, 'last_name' => $this->faker->lastName,
@ -42,20 +42,48 @@ class ClientTest extends TestCase
'terms_of_service' => 1 'terms_of_service' => 1
]; ];
$response = $this->post('/signup', $data);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
])->post('/api/v1/signup', $data);
$response->assertStatus(200) $response->assertStatus(200)
->assertJson([ ->assertJson([
'first_name' => $data['first_name'], 'first_name' => $data['first_name'],
]); ]);
$acc = $response->json();
$account = Account::find($acc['id']);
$token = $this->account->default_company->tokens()->first()->token;
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $token,
])->get('/api/v1/clients');
$response->assertStatus(200);
} }
public function testUserCreated() public function testClientShow()
{ {
$this->assertTrue(true); $account = Account::all()->first();
} $token = $account->default_company->tokens()->first()->token;
$client = $account->default_company->clients()->first();
$this->assertEquals(var_dump($account->default_company->clients->first()),1);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $token,
])->get('/api/v1/clients/'.$client->id);
$response->assertStatus(200);
}
} }

View File

@ -16,8 +16,9 @@ use Tests\TestCase;
class LoginTest extends TestCase class LoginTest extends TestCase
{ {
use DatabaseTransactions; //use DatabaseTransactions;
use UserSessionAttributes; use UserSessionAttributes;
use RefreshDatabase;
public function setUp() public function setUp()
{ {

View File

@ -22,7 +22,7 @@ use Tests\TestCase;
class MultiDBUserTest extends TestCase class MultiDBUserTest extends TestCase
{ {
//use DatabaseMigrations; //use DatabaseMigrations;
use InteractsWithDatabase; //use InteractsWithDatabase;
public function setUp() public function setUp()
{ {

View File

@ -17,7 +17,7 @@ use Tests\TestCase;
*/ */
class UniqueEmailTest extends TestCase class UniqueEmailTest extends TestCase
{ {
use InteractsWithDatabase; //use InteractsWithDatabase;
protected $rule; protected $rule;