PHPUnit - is this the correct way - yii

I am staring out with PHPUnit today. As i use Yii framework i am using the built in functions.
Can someone let me know if i am proceeding correctly
Here is the model function
public function getTaxRate()
{
if($this->province_id != 13 && $this->province_id != 14)
{
return 21;
}
elseif($this->identification[0] == 'B')
{
return 0;
}
else
{
return 7;
}
}
Here is the test case
public function testgetTaxRate()
{
$accountData = array(
array('identification'=>'x2', 'province_id'=>'50', 'result'=>21), // test for 21
array('identification'=>'x2', 'province_id'=>'13', 'result'=>7), // test for 7
array('identification'=>'B2', 'province_id'=>'13', 'result'=>0), // test for 0
);
foreach($accountData as $account)
{
$acc = new Accounts();
$acc->identification=$account['identification'];
$acc->province_id=$account['province_id'];
$tax = $acc->getTaxRate();
$this->assertEquals($tax, $account['result']);
}
}
Am I doing this correctly, The result is correct and it errors when I expect it to.
Regards

There is a good rule for organizing tests: one test per one case. If you have only one logic, you should use data provider mechanism (provided by PHPUnit) for this purpose in order to avoid code duplication.
Docs: http://phpunit.de/manual/3.7/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
And an example for you:
/**
* #dataProvider dataProvider_getTaxRate
*/
public function testGetTaxRate($id, $provinceId, $expectedResult)
{
$acc = new Accounts();
$acc->identification = $id;
$acc->province_id = $provinceId;
$this->assertEquals($expectedResult, $acc->getTaxRate());
}
public static function dataProvider_getTaxRate()
{
return array(
array('x2', '50', 21),
array('x2', '13', 7),
array('x2', '14', 7),
array('B2', '13', 0),
);
}
One more thing - it's good habit that expectations should be at first argument in assertions.

Related

How can I create a factory for classes? Getting "undeclared name" error

I have this code:
class kg is Dimension {
method new() {
return self.bless(
:type('mass'),
:abbr('kg'),
:multiplier(Multiplier.new(
numerator => 1.0,
denominator => Quantity.new(1000.0, 'g')))),
}
}
class mg is Dimension {
method new() {
return self.bless(
:type('mass'),
:abbr('mg'),
:multiplier(Multiplier.new(
numerator => 1000.0,
denominator => Quantity.new(1.0, 'g')))),
}
}
I'll be adding many more similar classes. Rather than spell out all these classes separately, I'd like to learn how to create a factory that can create these classes from simple data structures.
How do I do this? I read the Metaobject Protocol doc but I couldn't figure out how to give my classes different names on the fly based on the examples at the top and middle of the doc page.
I tried:
constant A := Metamodel::ClassHOW.new_type( name => 'A' );
A.^add_method('x', my method x(A:) { say 42 });
A.^add_method('set', my method set(A: Mu \a) { A.^set_name(a) });
A.^compose;
my $bar = A;
$bar.set('Foo');
say $bar.^name; #
A.x; # works
Foo.x; # error
But the last line just throws an error:
Undeclared name:
Foo used at line 13
The first thing you should realize, that any kind of meta-programmming usually will need to be done at compile time, aka in a BEGIN block.
Secondly: at the moment, Raku has some meta-programming features for creating code, but not all features needed to make this as painless as possible. The work on RakuAST will change that, as it basically makes Raku itself being built from a public meta-programming API (rather than you could argue, the current bootstrap version using a lot of NQP).
I've rewritten your code to the following:
sub frobnicate(Str:D $name) {
my \A := Metamodel::ClassHOW.new_type(:$name);
A.^add_method('x', my method x() { say 42 });
A.^compose;
OUR::{$name} := A;
}
BEGIN frobnicate("Foo");
say Foo.^name; # Foo
Foo.x; # 42
So, this introduces a sub called frobnicate that creates a new type with the given name. Adds a method x to it, and composes the new type. And then makes sure it is known as an our in the current compilation unit.
Then the frobnicate sub is called at compile time by prefixing a BEGIN. This is important, because otherwise Foo won't be known when the next line is compiled, so you'd get errors.
There is currently a small catch:
dd Foo.^find_method("x").signature; # :(Mu: *%_)
The invocant constraint is not set. I haven't found a way (before RakuAST) to set that using an meta-programming interface. But I don't think that's going to be an issue for the example you've given. If it does become an issue, then let's cross that bridge when we get there.
Here is the entire code that I came up with for a solution:
#!/usr/bin/env raku
use v6.d;
class Dimension { }
sub dimension-attr-factory($name, Mu $type, Mu $package) {
return Attribute.new(
:name('$.' ~ $name),
:type($type),
:has_accessor(1),
#:is_required(1),
:package($package)
);
}
sub dimension-factory(Str:D $name, #attributes) {
my \A := Metamodel::ClassHOW.new_type(:$name);
A.^add_parent(Dimension);
for #attributes {
my $attr = dimension-attr-factory($_[0], $_[1], A);
A.^add_attribute($attr);
}
A.^compose;
OUR::{$name} := A;
}
class Multiplier {
has Rat $.numerator;
has Quantity $.denominator;
method factor() {
return $.numerator / $.denominator.value;
}
}
class Quantity {
has Rat() $.value is required;
has Dimension:D $.dimension is required;
multi submethod BUILD(Rat:D() :$!value, Dimension:D :$!dimension) {
}
multi submethod BUILD(Rat:D() :$value, Str:D :$dimension) {
$!dimension = ::($dimension).new;
}
multi method new(Rat:D() $value, Dimension:D $dimension) {
return self.bless(
:$value,
:$dimension,
)
}
multi method new(Rat:D() $value, Str:D $dimension) {
return self.bless(
:$value,
:$dimension,
)
}
method to(Str:D $dimension = '') {
my $from_value = $.value;
my $to = $dimension ?? ::($dimension).new !! ::(self.dimension.abbr).new;
# do types match?
if $to.type ne self.dimension.type {
die "Cannot convert a " ~ self.dimension.type ~ " to a " ~ $to.type;
};
my $divisor = $.dimension.multiplier ?? $.dimension.multiplier.factor !! 1.0;
my $dividend = $to.multiplier ?? $to.multiplier.factor !! 1;
my $result = $dividend / $divisor * $from_value;
return Quantity.new($result, $to);
}
method gist() {
$.value ~ ' ' ~ $.dimension.abbr;
}
}
BEGIN {
my %dimensions = 'mass' => {
base => {
abbr => 'g',
},
derived => {
kg => { num => 1000.0, den => 1.0, },
mg => { num => 1.0, den => 1000.0, },
ug => { num => 1.0, den => 1000000.0, },
}
};
for %dimensions.kv -> $key, $value {
# set up base class for dimension type
my $base = %dimensions{$key}<base><abbr>;
my #attributes = ['abbr', $base], ['type', $key];
dimension-factory( $base, #attributes);
my %derived = %dimensions{$key}<derived>;
for %derived.kv -> $abbr, $values {
my $numerator = %dimensions{$key}<derived>{$abbr}<num>;
my $denominator = %dimensions{$key}<derived>{$abbr}<den>;
my $multiplier = Multiplier.new(
numerator => 1.0,
denominator => Quantity.new(1000.0, 'g'),
);
#attributes = ['abbr', $abbr], ['type', $key], ['multiplier', $multiplier];
my $dim = dimension-factory( $abbr, #attributes );
#$dim.new(:$abbr, type => $key, :$multiplier );
}
}
}
my $kg = kg.new();
my $quant = Quantity.new(value => 5.0, dimension => $kg);
dd $quant;
I would probably create a dimension keyword with a custom metamodel, would probably also override * and / operators using undefined dimensions and then would create kg with something like:
dimension Gram {
has Dimension::Type $.type = mass;
has Str $.abbr = "g";
}
dimension KiloGram is Gram {
has Str $.abbr = "kg";
has Dimension::Multiplier $.multiplier = 1000 * g;
}
dimension MiliGram is Gram {
has Str $.abbr = "mg";
has Dimension::Multiplier $.multiplier = g / 1000;
}
but maybe that's too much...

Nested Loop select the minimum defined value asp.net

I have a list of states, which are defined to be ordered by min to max. the sequence is the following:
Cancelled - complete - draft - reservation - reserved - ordered - confirmed
So the cancelled is the minimum state, and confirmed is the maximum state. I may have different instances with different states, so I use a for-each loop to run through all states, and select the minimum state present in the loop.
That is: if in a list I have states [complete, reserved, draft, ordered] I need to check all the values and select complete -as it appears to be the minimum state. OR
if I have [reserved, confirmed, ordered, draft, cancelled, confirmed, confirmed] I need to select the cancelled value, as it appears to be the minimum.
I am doing the following check, but it does not seem to be working:
string globstatus = " ";
foreach (var currentstatus in list)
{
if (currentstatus == "cancelled")
{
globstatus = "cancelled";
}
else
{
if (globstatus == "cancelled")
{
return globstatus;
}
else
{
if (currentstatus == "complete")
{
globstatus = "complete";
}
else
{
if (globstatus == "complete")
{
return globstatus;
}
else
{
if (currentstatus == "draft")
{
globstatus = "draft";
}
else
{
if (globstatus == "reservation")
{
return globstatus;
}
else
{
if (currentstatus == "reserved")
{
globstatus = "reserved";
}
else
{
if (globstatus == "ordered")
{
return globstatus;
}
else
{
if (currentstatus == "confirmed")
{
globstatus = "confirmed";
}
else
{
return currentstatus;
}
}
}
}
}
}
}
}
}
}
return globstatus;
What can be the best solution to achieve the desired behavior?
I find a rule of thumb helpful that if I need more than three levels of braces, I need to rethink my code. It's hard to follow, easy to make mistakes, and a nightmare to debug. I suggest that applies here - trying to follow the flow of what all those nested if..else statements is extremely difficult.
Using Enum
My preferred solution is to achieve this using an Enum, e.g.:
var list = new List<Status>
{
Status.Complete,
Status.Draft,
Status.Draft,
Status.Confirmed
};
var minStatus = (Status)list.Select(l => (int)l).Min();
// minStatus = Status.Complete
public enum Status
{
Cancelled,
Complete,
Draft,
Reservation,
Reserved,
Ordered,
Confirmed
}
How it works: by default Enums give each value a zero-based integer, i.e. Cancelled = 0, Complete = 1 and so on. You can override this with your own values if you wish (e.g. 1/2/4/8/16 if you want to combine multiple values).
I recommend using Enum types for things like this, rather than strings. It helps avoid typos, gives someone else looking at your code a clear understanding of how your program works and its flow, and represents hierarchy in a way in which simple strings don't. (For example - does 'complete' come before or after 'draft'? Without context, I imagine most people would say after, but in this case it comes before - that is much more obvious when using an Enum.)
Parse strings to Enum
However if the statuses have to be strings, you could parse them into an enum like so:
var stringList = new List<string>
{
"complete",
"draft",
"draft",
"confirmed",
"this will be ignored"
};
var statusList = new List<int>();
foreach (var str in stringList)
{
if(Enum.TryParse(typeof(Status), str, ignoreCase: true, out object? parsed) && parsed is Status status)
{
statusList.Add((int)status);
}
}
var minStatus = (Status)statusList.Min();
// minStatus = Status.Complete
However, if it's possible to refactor your code to use the Enum in the first place, that would be a better solution, and much quicker as parsing strings has an overhead that would be good to avoid.

How to merge collections based on object's particular key's value match in laravel6?

I have three collections.
SalaryCollection
BonusCollection
Deduction Collection
All of them have date which is common in some of them.
I want to merge these three into one collection in a way that object with same date in three becomes one as a result.
Something like this:
#items:array:2[
0=>{
+"date":"1-2020"
+"salaries":36500.0
+"deductions":1500.0
+"bonuses":7000.0
}
1=>{
+"date":"2-2020"
+"salaries":20000.0
+"deductions":1000.0
+"bonuses":5000.0
}
]
How can i do it?
I am not sure if this is the best way to do it but this is how i made it worked.
$salaryCollection = $salaryCollection->map(function ($item, $key) use ($bonusCollection) {
$single_bonus = $bonusCollection->where('date', $item->date);
if (!$single_bonus->isEmpty()) {
return collect($item)->put('bonuses', $single_bonus->first()->bonuses);
} else {
return collect($item)->put('bonuses', 0);
}
});
$salaryCollection = $salaryCollection->map(function ($item, $key) use ($deductionCollection) {
$single_deduction = $deductionCollection->where('date', $item['date']);
if (!$single_deduction->isEmpty()) {
return collect($item)->put('deductions', $single_deduction->first()->deductions);
} else {
return collect($item)->put('deductions', 0);
}
});

working with option of $query in yii2

i want use where for $query.
foreach ($oppId as $o) {
$id = $o['opportunity_id'];
$query->Where("id=$id");
}
When I use this. All items shown
$query->orWhere("id=$id");
i need get this query :
SELECT * FROM `opportunity` WHERE id =27 or id =28
this is all of my function :
public function actionShow($type = 0, $city = 0, $client = 0) {
$query = (new \yii\db\Query())->select(['*'])->from('opportunity ')->innerJoin('profile_details', 'opportunity.user_id=profile_details.user_id')->orderBy('id desc');
$query->Where('id !=-1');
if (isset($_REQUEST['type'])) {
$type = $_REQUEST['type'];
if ($type != 0) {
$query->andWhere("project_type_id=$type");
}
}
if (isset($_REQUEST['city'])) {
$city = $_REQUEST['city'];
if ($city != 0) {
$query->andWhere("state_id=$city");
}
}
if (isset($_REQUEST['client'])) {
$client = $_REQUEST['client'];
if ($client != 0) {
$oppId = \app\models\OpportunityControl::find()
->where('project_type_id = :project_type_id', [':project_type_id' => $client])
->all();
foreach ($oppId as $o) {
$id = $o['opportunity_id'];
$query->orWhere("id=$id");
}
}
}
You very much do not want to use strings to add to the query under any circumstances as that is ripe for SQL injection. I'd format it like this:
...
$params = [];
foreach ($oppId as $o) {
$params[] = $o->opportunity_id;
}
$query->andWhere(['in', 'id', $params]);
...
You should also adjust your other query params so that you are not passing variables into SQL via a string.
if (isset($_REQUEST['type'])) {
$type = $_REQUEST['type'];
if ($type != 0) {
$query->andWhere(['project_type_id' => $type]);
}
}
if (isset($_REQUEST['city'])) {
$city = $_REQUEST['city'];
if ($city != 0) {
$query->andWhere(['state_id' => $city]);
}
}
See the Yii2 guide on using variables in queries for what you are trying to avoid here. Specifically:
Do NOT embed variables directly in the condition like the following, especially if the variable values come from end user inputs, because this will make your application subject to SQL injection attacks.
// Dangerous! Do NOT do this unless you are very certain $status must be an integer.
$query->where("status=$status");
I do it with Arrays
$query->where(['or',['id'=>27],['id'=>28]]);
But in your case save all ids in a Array is not possible,I do it with string inside foreach
$StringWhere='';
$LastElement = end($oppId);
foreach ($oppId as $o)
{
$id = $o['opportunity_id'];
$StringWhere.=' id='.$id;
if($o!=$LastElement)
{
$StringWhere.=' or ';
}
}
$query->where($StringWhere);
$query->where(['or',['id'=>27],['id'=>28]]);
I use this and it works perfectly as mentioned by metola. :)

Sku not working in magento product update API

$result = $client->call($session, 'catalog_product.update', array('123', array(
'name' => 'Product333222'
)
)
);
Here '123' is the Sku of product. Sku is not working here in update Api.
If i give Product ID in place of Sku it is working fine.
So what is the Issue behind that.
If anyone Knows please let me know.
Thanks.
Magento is a bit dull here.
Long story short:
If you are using a numeric value without specifying an identification type its assuming you are doing your works on a product id. If you where to insert "abc" as a value (not numeric) it will be treated as if it were a SKU.
Best way to solve this is to use an identification type (in your case "SKU") in your api call.
Please see this for more info on using the identification type. http://www.magentocommerce.com/api/soap/catalog/catalogProduct/catalog_product.update.html
Or see: Magento 1.5, numeric SKUs and productIdentifierType
Short story long:
The following function gets called trough the api
app/code/core/Mage/Catalog/Model/Api/Resource.php
protected function _getProduct($productId, $store = null, $identifierType = null)
{
$product = Mage::helper('catalog/product')->getProduct($productId, $this->_getStoreId($store), $identifierType);
if (is_null($product->getId())) {
$this->_fault('product_not_exists');
}
return $product;
}
As you can see that function is calling the following function in the product helper:
public function getProduct($productId, $store, $identifierType = null) {
$loadByIdOnFalse = false;
if ($identifierType == null) {
if (is_string($productId) && !preg_match("/^[+-]?[1-9][0-9]*$|^0$/", $productId)) {
$identifierType = 'sku';
$loadByIdOnFalse = true;
} else {
$identifierType = 'id';
}
}
/** #var $product Mage_Catalog_Model_Product */
$product = Mage::getModel('catalog/product');
if ($store !== null) {
$product->setStoreId($store);
}
if ($identifierType == 'sku') {
$idBySku = $product->getIdBySku($productId);
if ($idBySku) {
$productId = $idBySku;
}
if ($loadByIdOnFalse) {
$identifierType = 'id';
}
}
if ($identifierType == 'id' && is_numeric($productId)) {
$productId = !is_float($productId) ? (int) $productId : 0;
$product->load($productId);
}
return $product;
}
Without specifying an $identifierType here and using a sku like '123' the thrid line is going to do a preg match with will result in true. Thus using its else function threating it as an ID in stead of sku.
In the end:
So, do your call like:
$result = $client->call($session, 'catalog_product.update', array('123', array(
'name' => 'Product333222'
), null, 'sku'));