PHPSpec: How to handle data heavy mocks in spec? - php-7

I have a series of specs that are doing what I would like them to but I'm wondering if I'm overcomplicating things as my let function for some of them is rather large and cumbersome.
I have built specs for a series of classes that process responses from SQL or JSON API depending on the response. The specs are just checking the resultant object of the processes of each class. I've mocked the connection to return valid mock data for each type of request that the specs would trigger. I'm trying to think of a better way to provide this mock data than having rather large arrays and JSON strings just sitting in the spec files.
For example (simplified):
class CharacterProcessorSpec extends ObjectBehavior
{
public function let(AdapterInterface $adapter)
{
$characters = [
[
'name' => 'Timmy',
'class' => 'Fighter',
'level' => 1,
'race' => 'Elf',
'str' => 16,
'dex' => 14,
'con' => 18,
'int' => 10,
'wis' => 12,
'cha' => 11,
... // Rest of the minimally required fields
], [
... // Second character for processing multiple at once
]
];
$adapter->fetch(new CharacterRequest('Timmy'))->willReturn([$characters[0]]);
$adapter->fetch(new CharacterRequest('*'))->willReturn($characters);
$this->beConstructedWith($adapter);
}
public function it_should_build_requested_character_details()
{
$this->build('Timmy')->shouldReturnArrayOfCharacters();
}
public function it_should_build_all_character_details()
{
$this->buildAll()->shouldReturnArrayOfCharacters();
}
public function getMatchers()
{
return [
'returnArrayOfCharacters' => function($characters) {
foreach ($characters as $c) {
if (!$c instanceof Character) {
return false;
}
}
return true;
}
];
}
}
Is it worth me moving the arrays to a separate file and loading them in or is that a no no?
Note: The build functions are designed to not care if the adapter is for SQL or the API, it just converts the data into a consistant object. Therefore the spec does not actually define what the data is (in the example I have provided it's the same as an SQL response but I usually have the second entry formatted how the JSON response would be as it processes on a per entry basis).

Is it worth me moving the arrays to a separate file and loading them in or is that a no no?
No, is not worth this effort. You provide only data to constructor and with that it should be OK in direct definitions.

Related

AsyncAPI enum with specific values

Is it possible to create an enum with specific values in AsyncAPI? In my legacy c# code theres an enum like this
public enum OrderStatus
{
Ordered = 30,
UnderDelivery = 40,
Deliveret = 50,
Cancelled = 99
}
I would like to create the same enum, with the same values in AsyncAPI. But it seems that you cannot specify the values in async API. Am I missing something? Is there an alternative solution?
It is not, or at least not in the general sense.
With AsyncAPI 2.4 you can define payloads with different schema formats (schemaFormat), by default this is an AsyncAPI Schema Object which is a superset of JSON Schema Draft 7. There might exist a schema format you can use that allows this, I just don't recall knowing one, for example not even JDT allows this.
Looking into JSON Schema Draft 7, you will not find it possible to define an enum value with an associated name such as Ordered and UnderDelivery. This means the only way to define it is something like this:
{
"title": "OrderStatus",
"type": "number",
"enum": [
30,
40,
50,
99
]
}
This is because JSON Schema focuses on validating JSON data, and in the JSON world, there is no such thing as an associated name to the enum value. That is entirely related to programming languages.
Solution
There are a couple of solutions and how you can proceed, let me highlight one way I see it could be achieved.
Specification Extensions
If you use the default AsyncAPI 2.4.0 Schema Object, AsyncAPI allows you to add your own extension such as:
{
"title": "OrderStatus",
"type": "number",
"enum": [
30,
40,
50,
99
],
"x-enumNames": {
30: "Ordered",
40: "UnderDelivery",
50: "Deliveret",
99: "Cancelled"
}
}
This will also work if you use pure JSON Schema draft 7 because any extra properties are allowed.
In newer versions of JSON Schema, they introduce something called vocabularies, which could standardize this feature. I started some work around a code generation vocabulary unfortunately, there are many other areas that need to be solved first, so I don't have the bandwidth to personally push it forward.
Generating the enum model
Regardless of how you actually define it with the specification, I expect you want tooling to generate the "accurate" enum model in code generation so here is one way to do it.
Modelina is an open-source tool that is being developed exactly for these cases. I have added an example test case to showcase how it could be done for Modelina v0.59.
Let me break the implementation down:
const generator = new CSharpGenerator({
presets: [
{
enum: {
item: ({model, item, content}) => {
// Lets see if an enum has any associated names
const hasCustomName = model.originalInput !== undefined && model.originalInput['x-enumNames'] !== undefined;
if (hasCustomName) {
// Lets see if the specific value has an associated name
const customName = model.originalInput['x-enumNames'][item];
if (customName !== undefined) {
return customName;
}
}
return content;
}
}
}
]
});
The Csharp generator is being instructed to use a custom preset (can be seen as a Node.js middleware) for the enum renderer. Here we add a middleware to overwrite the name of each enum "item"/value based on whether it has our extension or not.
This results in the following generated model:
public enum OrderStatus
{
Ordered,
UnderDelivery,
Deliveret,
Cancelled
}
public static class OrderStatusExtensions
{
public static dynamic GetValue(this OrderStatus enumValue)
{
switch (enumValue)
{
case OrderStatus.Ordered: return 30;
case OrderStatus.UnderDelivery: return 40;
case OrderStatus.Deliveret: return 50;
case OrderStatus.Cancelled: return 99;
}
return null;
}
public static OrderStatus? ToOrderStatus(dynamic value)
{
switch (value)
{
case 30: return OrderStatus.Ordered;
case 40: return OrderStatus.UnderDelivery;
case 50: return OrderStatus.Deliveret;
case 99: return OrderStatus.Cancelled;
}
return null;
}
}

CakePHP 3.x - Modify user data in Auth component before session creation

When using the Auth component in CakePHP 3 you can define the findAuth() finder (or configure a different finder) to have control over what data is loaded:
// AppController
$this->loadComponent('Auth', [
//...
'authenticate' => [
'Form' => [
'finder' => 'auth'
]
],
//...
]);
// UsersTable
public function findAuth($query, array $options)
{
return $query
->...;
}
I need some functionality that cannot be done with the query builder. How can I post-process the loaded auth data before session creation?
Note that I have different ways of logging in my users, so I would prefer this be kept inside the AuthComponent logic.
(This is still for CakePHP 3, but a brief comment on how this could be done in the new CakePHP 4 Authentication plugion would also be appriciated.)
EDIT: Rough outline of what I need: data needs to re-organised in the users array based on current context, i.e. users can have an active project selected.
I'm still not really sure what exactly you need to re-organize in what way exactly, but generally you can modify the queried data using mappers/reducers and result formatters, the latter usually being the easier way.
Here's a quick example that would add an additional field named additional_data to the result in case a field named active_project_id is set:
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($row) {
if (isset($row['active_project_id'])) {
$row['additional_data'] = 'lorem ipsum';
}
return $row;
});
});
Such a finder query would work with the new authentication plugin too.
See also
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields

Can't insert into database with save()

I am having an issue inserting a record into the database. I am a beginner with the Yii framework, so I may have made some stupid mistakes.
This is from the SiteController
public function actionCreatePost(){
$model = new PostForm();
$post = new Post();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$post->body = $model->body;
$post->title = $model->title;
$post->save();
return $this->redirect('index');
}else {
return $this->render('createPost', ['model' => $model]);
}
}
This is from the Post class
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'created_at',
'updatedAtAttribute' => 'updated_at',
'value' => new Expression('NOW()'),
],
[
'class' => BlameableBehavior::className(),
'createdByAttribute' => 'id_author',
]
];
}
The issue is that you have created a PostForm class for the form (which is correct) but you are then trying to load the response into the Post class - a completely different class. This won’t work without modification.
If you have a look at the response:
var_dump(Yii:$app->request->post());
You will see the form data is located within the PostForm key. Yii will therefore only load the data into the PostForm class.
The correct solution is therefore to create a savePost() function within the PostForm eg:
public function savePost(){
$model = new Post();
$model->propertyABC = $this->propertyABC
...etc...
$model->save();
So the action would appear as follows:
$model = new PostForm();
If($model->load(Yii::$app->request->post()) && $model->validate()){
$model->savePost();
The other option is to rename the key from PostForm to Post. Yii will then load the data but this is not the best approach as it is a bit obscure.
Hope that helps
I would guess the issue is with the validation.
I can see several issues I will point out. First, I cannot figure out why are you creating a new PostForm, loading the data in it and verifying it, just to dump some values in a new Post and save it. Are there some functions, you are running in the PostForm model, that are triggered by load or verify? If that is not the case, I would suggest dropping one of the models, and using only the other. Usually, that is the Form model. It serves as a link between the ActiveForm and the model handling everything. You can do everything in the createPost() function in the Form model, and then in the controller it will look like
if ($model->load(Yii::$app->request->post())) {
$model->save();
return $this->redirect('index');
}
Second of all, you can dump post->getErrors() before the save to see if there are any errors with the validation. What you can also do, is call $post->save(false) instead. If you pass false to it, it will not trigger $post->validate(), and some errors can be neglected. Please, let me know if there is anything unclear.

Undefined model by using Join Query in Laravel

I am indexing apples with their specified properties (such as color) using API Laravel. I use join to retrieve apples which are related to a specified brand. but it does not retrieve apples with their own specified properties which are defined in another DB and models.
public function index(Brand $brand)
{
$apples = Apple::join('brands', 'brand_id', 'brands.id')->where('brand_id', $brand->id)->get();
return returnSuccessfulResponse(
trans('api.response.successful.index'),
Resource::collection($apples)
);
}
Apple model:
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function appleProperties()
{
return $this->hasMany(AppleProperty::class);
}
Resource:
return [
'id' => $this->brand->id,
'name' => $this->brand->name,
'apple-properties' => $this->appleProperties,
];
Route:
Route::apiResource('brands/{brand}/apples', 'AppleController');
It is not retrieving appleProperties. I do not understand that reason!
When you use join() method in your queries, it is recommended to use select() as well, so that is no longer ambiguous which table you referenced to. In your code, the query may be something like this:
$apples = Apple::join('brands', 'brand_id', 'brands.id')->select('apples.*')->where('brand_id', $brand->id)->get();

Sproutcore datasources and model relationships

I currently have a Sproutcore app setup with the following relationships on my models:
App.Client = SC.Record.extend({
name: SC.Record.attr(String),
brands: SC.Record.toMany('App.Brand', {isMaster: YES, inverse: 'client'})
});
App.Brand = SC.Record.extend({
name: SC.Record.attr(String),
client: SC.Record.toOne('App.Client, {isMaster: NO, inverse: 'brands'})
});
When I was working with fixtures my fixture for a client looked like this:
{
guid: 1,
name: 'My client',
brands: [1, 2]
}
And my fixture for a brand looked like this:
{
guid: 1,
name: 'My brand',
client: 1
}
Which all worked fine for me getting a clients brands and getting a brands client.
My question is in regards to how Datasources then fit into this and how the server response should be formatted.
Should the data returned from the server mirror exactly the format of the fixtures file? So clients should always contain a brands property containing an array of brand ids? And vice versa.
If I have a source list view which displays Clients with brands below them grouped. How would I go about loading that data for the source view with my datasource? Should I make a call to the server to get all the Clients and then follow that up with a call to fetch all the brands?
Thanks
Mark
The json you return will mostly mirror the fixtures. I recently had pretty much the same question as you, so I built a backend in Grails and a front end in SC, just to explore the store and datasources. My models are:
Scds.Project = SC.Record.extend(
/** #scope Scds.Project.prototype */ {
primaryKey: 'id',
name: SC.Record.attr(String),
tasks: SC.Record.toMany("Scds.Task", {
isMaster: YES,
inverse: 'project'
})
});
Scds.Task = SC.Record.extend(
/** #scope Scds.Task.prototype */ {
name: SC.Record.attr(String),
project: SC.Record.toOne("Scds.Project", {
isMaster: NO
})
});
The json returned for Projects is
[{"id":1,"name":"Project 1","tasks":[1,2,3,4,5]},{"id":2,"name":"Project 2","tasks":[6,7,8]}]
and the json returned for tasks, when I select a Project, is
{"id":1,"name":"task 1"}
obviously, this is the json for 1 task only. If you look in the projects json, you see that i put a "tasks" array with ids in it -- thats how the internals know which tasks to get. so to answer your first question, you dont need the id from child to parent, you need the parent to load with all the children, so the json does not match the fixtures exactly.
Now, it gets a bit tricky. When I load the app, I do a query to get all the Projects. The store calls the fetch method on the datasource. Here is my implementation.
Scds.PROJECTS_QUERY = SC.Query.local(Scds.Project);
var projects = Scds.store.find(Scds.PROJECTS_QUERY);
...
fetch: function(store, query) {
console.log('fetch called');
if (query === Scds.PROJECTS_QUERY) {
console.log('fetch projects');
SC.Request.getUrl('scds/project/list').json().
notify(this, '_projectsLoaded', store, query).
send();
} else if (query === Scds.TASKS_QUERY) {
console.log('tasks query');
}
return YES; // return YES if you handled the query
},
_projectsLoaded: function(response, store, query) {
console.log('projects loaded....');
if (SC.ok(response)) {
var recordType = query.get('recordType'),
records = response.get('body');
store.loadRecords(recordType, records);
store.dataSourceDidFetchQuery(query);
Scds.Statechart.sendEvent('projectsLoaded')
} else {
console.log('oops...error loading projects');
// Tell the store that your server returned an error
store.dataSourceDidErrorQuery(query, response);
}
}
This will get the Projects, but not the tasks. Sproutcore knows that as soon as I access the tasks array on a Project, it needs to get them. What it does is call retrieveRecords in the datasource. That method in turn calls retrieveRecord for every id in the tasks array. My retrieveRecord method looks like
retrieveRecord: function(store, storeKey) {
var id = Scds.store.idFor(storeKey);
console.log('retrieveRecord called with [storeKey, id] [%#, %#]'.fmt(storeKey, id));
SC.Request.getUrl('scds/task/get/%#'.fmt(id)).json().
notify(this, "_didRetrieveRecord", store, storeKey).
send();
return YES;
},
_didRetrieveRecord: function(response, store, storeKey) {
if (SC.ok(response)) {
console.log('succesfully loaded task %#'.fmt(response.get('body')));
var dataHash = response.get('body');
store.dataSourceDidComplete(storeKey, dataHash);
} ...
},
Note that you should use sc-gen to generate your datasource, because it provides a fairly well flushed out stub that guides you towards the methods you need to implement. It does not provide a retrieveMethods implementation, but you can provide your own if you don't want to do a single request for each child record you are loading.
Note that you always have options. If I wanted to, I could have created a Tasks query and loaded all the tasks data up front, that way I wouldn't need to go to my server when I clicked a project. So in answer to your second question, it depends. You can either load the brands when you click on the client, or you can load all the data up front, which is probably a good idea if there isn't that much data.