I am using PHP, PHP Legacy Router and PHP Legacy Container.
I am using also PHPUnit test
I am trying to make a correct PHPUnit test, however, I am a begginer and I don`t know whether my code is ok or it is wrong.
In my NewUserService class I write 2 functions to get all emails and if the email is already existed in the db to return an error that user is created already.
My problem is how to test this functionality trough PHPUnit tests in my NewUserServiceTest
This is my functionality to test whether the user is already created
public function getAllUsersEmails()
{
$sql ="SELECT `email` FROM users";
$stmt = $this->container->get('db')->prepare($sql);
$stmt->execute();
if ($stmt->errorCode() != '00000') throw new Exception(sprintf(("Database error (%s): %s"), $stmt->errorCode(), $stmt->errorInfo()[2]));
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* #throws Exception
*/
private function repeatingEmailAddress($requestObj){
$allUsersEmails = NewUserService::getAllUsersEmails();
$allUsersEmailsValues = [];
foreach ($allUsersEmails as $value){
$allUsersEmailsValues[]=$value['email'];
}
if(in_array($requestObj['email'], $allUsersEmailsValues)){
throw new Exception("A user with that email is already created.", 400);
}
This is the unit test:
private function repeatingUserEmail(): array
{
$request = $this->createStub(ServerRequestInterface::class);
$requestJsonPayload = '{"email": "mj#hph.io", "role": 2, "first": "Griffin", "last": "Mitchell"}';
$requestStream = fopen('php://memory', 'r+');
fwrite($requestStream, $requestJsonPayload);
rewind($requestStream);
$request->method('getBody')->willReturn(new Stream($requestStream));
$routeArgs = ['id' => 4];
/* <container with user> */
$container = $this->getStandardContainer();
$user = $this->createMock(User::class);
$user->id = '1';
$user->role = '1';
$user->parent_entity = '11';
$user->email='mj#hph.io';
$container->add('current_user', $user);
$response = ['error' => 'A user with that email is already created.'];
$responseJsonPayload = json_encode($response);
$responseStream = fopen('php://memory', 'r+');
fwrite($responseStream, $responseJsonPayload);
rewind($responseStream);
$expectedResponse = $this->createMock(ResponseInterface::class);
$expectedResponse->method('getStatusCode')->willReturn(400);
$expectedResponse->method('getBody')->willReturn(new Stream($responseStream));
return [$request, $routeArgs, $container, $expectedResponse];
}
Is it correct? How should be done in a correct way?
First of all i suggest to make a isEmailExists($email) method that will search this specific email in the database and return bollean response (true/false). So you don't have to pull millions of emails from database and then loop through millions of rows and in the end you just throw an error.
Back to the test. I suggest to use the expectException assertion that you can find in the documentation
You can 1)add one row in you database and then try to pass the same email that will throw this error or 2)partial mock the
isEmailExists($email) to return true and throw your exception (without hit the database at all)
Related
I have been working around Bit-Wasp/bitcoin-php library for a while now and I encountered problems with it that I cannot resolve.
I have this as my code:
public function bitcoinWalletFromPublicKey($key, $index) {
$adapter = Bitcoin::getEcAdapter();
if (config('market.btc_network') == "mainnet") {
$btc = NetworkFactory::bitcoin();
$bitcoinPrefixes = new BitcoinRegistry();
} else {
$btc = NetworkFactory::bitcoinTestnet();
$bitcoinPrefixes = new BitcoinTestnetRegistry();
}
$slip132 = new Slip132(new KeyToScriptHelper($adapter));
$pubkeytype=substr($key, 0, 4);
if ($pubkeytype=='xpub' || $pubkeytype =='tpub') $pubPrefix = $slip132->p2pkh($bitcoinPrefixes);
if ($pubkeytype=='ypub') $pubPrefix = $slip132->p2shP2wpkh($bitcoinPrefixes);
if ($pubkeytype=='zpub' || $pubkeytype =='vpub') $pubPrefix = $slip132->p2wpkh($bitcoinPrefixes);
$config = new GlobalPrefixConfig([
new NetworkConfig($btc, [$pubPrefix])
]);
$serializer = new Base58ExtendedKeySerializer(
new ExtendedKeySerializer($adapter, $config)
);
$path = '0/' . $index;
$fkey = $serializer->parse($btc, $key);
$child_key = $fkey->derivePath($path);
#$account0Key = $child_key->derivePath("84'/0'/0'");
#$child_key = $fkey->derivePath("0/1");
//dd($child_key->getAddress(new AddressCreator())->getAddress());
return $child_key->getAddress(new AddressCreator())->getAddress();
}
I have two problems with this code:
Problem #1
On the first few lines of the code you will see that I used an If statement to check what network should it use. On my test im using testnet network and I'm sure as well that the code on my If / else { # code } works and it uses NetworkFactory::bitcoinTestnet() and new BitcoinTestnetRegistry() properly;
$key variable represents the Master Public Key of my user from Electrum wallet or whatever with a format of (xpub#########################/vpub#########################) or in my case since its on testnet it uses tpub######################### format. However, it returns an address with a format of bc#########, this means that its passing on mainnet network wherein it should be on testnet network.
Problem #2
On lower part of my code, I'm using $fkey = $serializer->parse($btc, $key); and $child_key = $fkey->derivePath($path) wherein $path = '0/' $index. $index here are just random numbers. It can be 0/1 or 0/99 or whatever 0/random.
Problem here is that somehow related to Problem #1, after it generates the wrong address, when I try to use this address for transaction my rpc returns an invalid address Error. As you can see as well I have a commented code $account0Key = $child_key->derivePath("84'/0'/0'"); wherein i got an error that it needs a private key instead of a public one. Now, my concern is that I do not want the users of the system i'm making to put their private keys whatsoever as it will might just compromise their wallets.
Basically, What I want to achieve using with this library from BitWasp is when a user put in their master public key from their wallet, my system would be able to then generate an address to be used for a btc transaction. Please help.
Passing the network inside the getAddress() method works
return $child_key->getAddress(new AddressCreator())->getAddress($btc);
I am trying to do a simple operation: create 2 entries; on each creation, first check if the entity exists, create it, if it does not, and then do the adjustment. In some cases (on first such operation) we need to create the entity in the first entry and work with it in the second. Unfortunately, this does not happen and we end up with two entries that are useless. After this initial issue, everything works as expected (i.e. on next iteration the entity is properly found).
Here is the code for the entries:
// Create first entry
$debitCode = 'bank';
$creditCode = 'equity';
// Create entry
$accountEntry = new AccountEntry();
$accountEntry->setAmount($amount);
$debitAccount = $unit->getAccountByType($debitCode);
if (!$debitAccount) {
// Create debit account
$debitAccountType = $em->getRepository('App:AccountType')->findOneBy(['code' => $debitCode]);
$debitAccount = new Account();
$debitAccount->setType($debitAccountType);
$em->persist($debitAccount);
}
$debitAccount->debit($amount);
$accountEntry->setDebitAccount($debitAccount);
$creditAccount = $unit->getAccountByType($creditCode);
if (!$creditAccount) {
// Create credit account
$creditAccountType = $em->getRepository('App:AccountType')->findOneBy(['code' => $creditCode]);
$creditAccount = new Account();
$creditAccount->setType($creditAccountType);
$em->persist($creditAccount);
}
$creditAccount->credit($amount);
$accountEntry->setCreditAccount($creditAccount);
$em->persist($accountEntry);
$em->flush();
// Create second entry
$debitCode2 = 'accountsPayable';
$creditCode2 = 'bank';
$accountEntry2 = new AccountEntry();
$accountEntry2->setAmount($amount);
$debitAccount = $unit->getAccountByType($debitCode2);
if (!$debitAccount) {
// Create debit account
$debitAccountType = $em->getRepository('App:AccountType')->findOneBy(['code' => $debitCode2]);
$debitAccount = new Account();
$debitAccount->setUnit($unit);
$debitAccount->setType($debitAccountType);
$em->persist($debitAccount);
}
$debitAccount->debit($amount);
$accountEntry2->setDebitAccount($debitAccount);
$creditAccount = $unit->getAccountByType($creditCode2);
if (!$creditAccount) {
// Create credit account
$creditAccountType = $em->getRepository('App:AccountType')->findOneBy(['code' => $creditCode2]);
$creditAccount = new Account();
$creditAccount->setUnit($unit);
$creditAccount->setType($creditAccountType);
$em->persist($creditAccount);
}
$creditAccount->credit($amount);
$accountEntry2->setCreditAccount($creditAccount);
$em->persist($accountEntry2);
$em->flush;
And here is the getAccountByType function:
/**
* Get Account by type of account.
*/
public function getAccountByType($code)
{
$filter = function ($account) use ($code) {
if ($account->getType()->getCode() == $code) {
return $account;
}
};
$accounts = $this->accounts->filter($filter)->getValues();
return isset($accounts[0]) ? $accounts[0] : null;
}
And, of course, 30 minutes after I posted the question, I found the answer myself (despite having been banging my head against the wall for a couple of days before posting).
Basically, we need to refresh the $unit entity after persisting/flushing the initial entry:
$em->refresh($unit);
Otherwise, the getAccountByType method apparently does not take into account the changes. So, it appears that entity methods do not take into account flushed changes to the database, if the entity is not refreshed. Probably basic stuff, but I did not know that. I hope this will save someone lots of trouble.
I am using Symfony 2.7, doctrine 2, MySQL.
I'm trying to retrieve the tables creation order in a controller as when issuing
php app/console doctrine:generate:schema --dump-sql
but I only need the table names.
So, for instance, if I have two tables like
Product - Category I'd like to have an output which looks like this : array('Category', 'Product')
Using this documentation here's what I've done so far:
public function getTablesCreationOrderAction()
{
$conn = $this->get('database_connection');
$sm = $conn->getSchemaManager();
$sequences = $sm->listSequences($this->getParameter('database_name'));
die();
}
An exception is thrown at $sequences = $sm->listSequences.
Here's the exception I get:
Operation 'Doctrine\DBAL\Platforms\AbstractPlatform::getListSequencesSQL' is not supported by platform.
I don't know if this means that MySQL does not support the operation.
Thanks!
Yes, but it seems like the feature isn't implemented yet, since this is in the source code of AbstractPlatform you will see this:
public function getListSequencesSQL($database)
{
throw DBALException::notSupported(__METHOD__);
}
Now this means that platforms overriding AbstractPlatform must override these functions to provide the right implementation.
Now if you take a look at MySqlPlatform, you won't be able to find the getListSequencesSQL() anywhere, hence you can't use this feature with mysql or at least it's not implemented.
For those interested, here's how I did it. There may be better ways..
public function getTablesCreationOrderAction()
{
$app = New Application($this->get('kernel'));
$app->setAutoExit(false);
$input = New ArrayInput(['command' => 'doctrine:schema:create',
'--dump-sql'=>true]);
$output = New BufferedOutput();
$app->run($input, $output);
$rst = $output->fetch();
$schema = explode('CREATE TABLE ', $rst);
$tables = [];
foreach ($schema as $tableDef)
{
$tables[] = explode(' (', $tableDef)[0];
}
unset($tables[0]); // empty
var_dump($tables);
die();
}
I'm setting up a Laravel 5.1 project and I've been making good progress with it but have run in to something that I'm struggling to figure out.
Basically, I have been creating objects to insert in to the database in my controller methods. This hasn't been too bad because they're usually one-liners.
However, I've run in to a more complex db entry and my controller has become a little muddied. Let me show you:
/**
* Store a new ticket for the specified project.
*
* #param int $id
* #param TicketsRequest $request
* #return Response
*/
public function store_ticket($id, TicketsRequest $request)
{
$user_id = Auth::user()->id;
$project_id = $id;
$project_tickets = Ticket::whereProjectId($id);
$project_ticket_id = $project_tickets->count() + 1;
$ticket = new Ticket;
$ticket->user_id = $user_id;
$ticket->project_id = $project_id;
$ticket->project_ticket_id = $project_ticket_id;
$ticket->title = $request->title;
$ticket->save();
$ticket_update = new TicketUpdate;
$ticket_update->user_id = $user_id;
$ticket_update->ticket_id = $ticket->id;
$ticket_update->status_id = $request->status_id;
$ticket_update->priority_id = $request->priority_id;
$ticket_update->assigned_to = $request->assigned_to;
$ticket_update->description = $request->description;
$ticket_update->save();
return redirect('/projects');
}
So as you can see, I'm creating a ticket which gets saved to the database, then also creating a ticket update which is also saved to the database.
What I'd like to do is extract this code in to 'something' to clean up my controller.
On my travels, I've found that maybe creating repositories might be the way forward. Otherwise I was thinking about some kind of service but I'm not really convinced that that is the way forward.
I have a subscription to Laracasts and found the following video but it's a little outdated and I was sure if this would still be the 'right' way to do this in Laravel 5.1 (I've found that things seem to have a natural home in 5.1 compared to older versions).
https://laracasts.com/lessons/repositories-simplified
Any suggestions/links etc would be great. Thanks!
If you instantiate your objects often/always using the same set of attributes, you could easily extract that code into models constructors, e.g:
//in your model
public function __construct($user_id, $ticket_id, $request) {
$this->user_id = $user_id;
$this->ticket_id = $ticket_id;
$this->status_id = $request->status_id;
$this->priority_id = $request->priority_id;
$this->assigned_to = $request->assigned_to;
$this->description = $request->description;
}
// in your controller
$ticket_update = new TicketUpdate($user_id, $ticket->id, $request);
$ticket_update->save();
Nothing wrong with your implementation. I suggest you use try catch when calling the save() method in case something goes wrong and send a nice error to the user saying something like "something went wrong, contact your admin".
I am currently working on a big application that uses monolog for logging and was asked to hide any sensitive information like passwords.
What I tried to do, was extending monolog so it would automatically replace sensitive information with asterics, but even though the data seems to be altered, in the end the original text gets logged.
use Monolog\Handler\AbstractProcessingHandler;
class FilterOutputHandler extends AbstractProcessingHandler
{
private $filteredFields = [];
public function __construct(array $filteredFields = [], $level = Monolog\Logger::DEBUG, $bubble = true)
{
$this->filteredFields = array_merge($filteredFields, $this->filteredFields);
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
foreach($record['context'] as $key=>$value){
if(in_array($key, $this->filteredFields)){
$record['context'][$key] = '*****';
}
}
return $record;
}
}
And when I initialize my logger I do this:
$logger->pushHandler(new FilterOutputHandler(['username', 'password']));
$logger->debug('Sensitive data incoming', ['username'=> 'Oh noes!', 'password'=> 'You shouldn\'t be able to see me!']);
I also tried overridding the handle and processRecord methods of the AbstractProcessingHandler interface but in vain. Can this be done in monolog?
Looks like I was trying the wrong thing.
Instead of adding a new handler to my logger, I had to add a new processor by using the pushProcessor(callable) method.
So, in my specific use case, I can add filters to my context like this:
function AddLoggerFilteringFor(array $filters){
return function ($record) use($filters){
foreach($filters as $filter){
if(isset($record['context'][$filter])){
$record['context'][$filter] = '**HIDDEN FROM LOG**';
}
}
return $record;
};
}
And later I can add filters simply by
(init)
$logger->pushProcessor(AddLoggerFilteringFor(['username', 'password']));
...
(several function definition and business logic later)
$logger->debug('Some weird thing happened, better log it', ['username'=> 'Oh noes!', 'password'=> 'You shouldn\'t be able to see me!']);