We would like to generate a living documentation for our team's TestCafe testing framework.
Instead of having separate wiki / separate document to maintain info about the framework, we're exploring option like JSDocs.
The JSDoc templates looks something like below:
/**
* Represents a book.
* #constructor
* #param {string} title - The title of the book.
* #param {string} author - The author of the book.
*/
So how to make them more meaningful for documenting our TestCafe tests?
Thanks
Test cases are function calls. JsDoc is not built to document function calls. It is primarily built to document classes, methods, and property declarations, not invocations.
One of the approaches would be to extract your test case code to a separate function and document it. For instance:
import { Selector } from 'testcafe';
fixture `My fixture`
.page `http://devexpress.github.io/testcafe/example/`;
test('Test a book', async t => {
test_a_book(t, 'title', 'author');
});
/**
* Represents a book.
* #param {string} title - The title of the book.
* #param {string} author - The author of the book.
*/
async function test_a_book(t, title, author) {
await t
.typeText('#title', title)
.typeText('#author', author)
.click('#submit-button')
.takeScreenshot({
path: 'books/book.png',
fullPage: true
});
}
You can also extract your test case logic to a Page Model where you can document everything.
Also, you can define the function invocation as a #property or create a custom #tag, but JsDoc does not produce nice-looking documentation for these workarounds.
Related
I started using ZAP and I really like it so far but I miss an option or maybe I don't find it. Burp has an payload mode called "pitchfork" where you can increment two payloads at a time. Got ZAP anything like this?
Thanks
I just realized that what I'd given you was actually Battering ram not Pitchfork.
https://portswigger.net/burp/documentation/desktop/tools/intruder/attack-types
For Pitchfork you'd simply define two fuzz locations and specify two different lists. Easy peasy.
Here's how you'd accomplish what you need.
Assume the following request for my answer/example:
GET http://localhost:8090/bodgeit/product.jsp?typeid=3&foo=3 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Host: localhost:8090
Let's say that typeid and foo are the param values that you want to pitchfork. Your going to create a Payload Generator script in ZAP, such as the following (this is a simple minor tweak to the default template, the important differences are outlined below after the code sample):
// Auxiliary variables/constants for payload generation.
var NUMBER_OF_PAYLOADS = 10;
var INITIAL_VALUE = 1;
var count = INITIAL_VALUE;
var MID= '&foo='
/**
* Returns the number of generated payloads, zero to indicate unknown number.
* The number is used as a hint for progress calculations.
*
* #return {number} The number of generated payloads.
*/
function getNumberOfPayloads() {
return NUMBER_OF_PAYLOADS;
}
/**
* Returns true if there are still payloads to generate, false otherwise.
*
* Called before each call to next().
*
* #return {boolean} If there are still payloads to generate.
*/
function hasNext() {
return (count <= NUMBER_OF_PAYLOADS);
}
/**
* Returns the next generated payload.
*
* This method is called while hasNext() returns true.
*
* #return {string} The next generated payload.
*/
function next() {
payload = count;
count++;
return payload+MID+payload;
}
/**
* Resets the internal state of the payload generator, as if no calls to
* hasNext() or next() have been previously made.
*
* Normally called once the method hasNext() returns false and while payloads
* are still needed.
*/
function reset() {
count = INITIAL_VALUE;
}
/**
* Releases any resources used for generation of payloads (for example, a file).
*
* Called once the payload generator is no longer needed.
*/
function close() {
}
Note: Declaration of the MID constant, which is the middle part of the string between the two param values. Modification of the next() method which returns the same value for both param values with the "MID" string inserted between.
In the request highlight 3&foo=3 right click and select "Fuzz...". Click the "Payloads" button, click the "Add" button, set the "Type" dropdown as "Script", select your "Script" by name in the dropdown (I called mine "Pitchfork"). ("Generate Preview" if you like.) Click the "Add" button. Click the "Ok" button. Click "Start Fuzzer". You've now run a "Pitchfork" fuzz in ZAP.
Results in the following payloads:
1&foo=1
2&foo=2
3&foo=3
4&foo=4
5&foo=5
6&foo=6
7&foo=7
8&foo=8
9&foo=9
10&foo=10
Things to keep in mind:
Assuming you're fuzzing a normal GET or POST you should be able to order the params however you like. (Targets "shouldn't" care which order params are in, you can copy/paste them into whatever order you need and send the request manually.) If it's some sort of well formed content (JSON/XML, or whatever) then you can just turn MID into a huge string...
You can install/use a scripting add-on such as Python (Jython) if you want to access payloads from a file.
If you wanted to process a header based on the same payload as the initial injection then you'd do a slight variation.
Create a "Fuzzer HTTP Processor" script, which is just a slight variation on the template. The following example simply checks the value of the payload in foo and uses it in a header:
/**
* Processes the fuzzed message (payloads already injected).
*
* Called before forwarding the message to the server.
*
* #param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* #param {HttpMessage} message - The fuzzed message, that will be forward to the server.
*/
function processMessage(utils, message) {
// To obtain the list of payloads:
// utils.getPayloads()
// To obtain original message:
// utils.getOriginalMessage()
// To stop fuzzer:
// utils.stopFuzzer()
// To increases the error count with a reason:
// utils.increaseErrorCount("Reason Error Message...")
// To send a message, following redirects:
// utils.sendMessage(myMessage)
// To send a message, not following redirects:
// utils.sendMessage(myMessage, false)
// To add a message previously sent to results:
// utils.addMessageToResults("Type Of Message", myMessage)
// To add a message previously sent to results, with custom state:
// utils.addMessageToResults("Type Of Message", myMessage, "Key Custom State", "Value Custom State")
// The states' value is shown in the column 'State' of fuzzer results tab
// To get the values of the parameters configured in the Add Message Processor Dialog.
// utils.getParameters()
// A map is returned, having as keys the parameters names (as returned by the getRequiredParamsNames()
// and getOptionalParamsNames() functions below)
// To get the value of a specific configured script parameter
// utils.getParameters().get("exampleParam1")
// Process fuzzed message...
var payload = null;
for (var iterator = message.getUrlParams().iterator(); iterator.hasNext();) {
var urlParam = iterator.next();
if (urlParam.getName() == 'foo') {
payload = urlParam.getValue();
break;
}
}
message.getRequestHeader().setHeader("X-Some-Id", payload);
}
/**
* Processes the fuzz result.
*
* Called after receiving the fuzzed message from the server.
*
* #param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
* #param {HttpFuzzResult} fuzzResult - The result of sending the fuzzed message.
* #return {boolean} Whether the result should be accepted, or discarded and not shown.
*/
function processResult(utils, fuzzResult){
// All the above 'utils' functions are available plus:
// To raise an alert:
// utils.raiseAlert(risk, confidence, name, description)
// To obtain the fuzzed message, received from the server:
// fuzzResult.getHttpMessage()
// To get the values of the parameters configured in the Add Message Processor Dialog.
// utils.getParameters()
// A map is returned, having as keys the parameters names (as returned by the getRequiredParamsNames()
// and getOptionalParamsNames() functions below)
// To get the value of a specific configured script parameter
// utils.getParameters().get("exampleParam1")
return true;
}
/**
* This function is called during the script loading to obtain a list of the names of the required configuration parameters,
* that will be shown in the Add Message Processor Dialog for configuration. They can be used
* to input dynamic data into the script, from the user interface
*/
function getRequiredParamsNames(){
return [];
}
/**
* This function is called during the script loading to obtain a list of the names of the optional configuration parameters,
* that will be shown in the Add Message Processor Dialog for configuration. They can be used
* to input dynamic data into the script, from the user interface
*/
function getOptionalParamsNames(){
return [];
}
You'd select just the param value you want to fuzz. In the above example if you wanted to fuzz foo you'd just select the 3. Setup the fuzzer much as above (you could use a built-in generator instead of a script), but add your "Message Processor" in the "Message Processors" tab, run the fuzzer.
Based on this example foo should get the values 1 thru 10 and each request will have a header such as X-Some-Id: 1 added (where the Id is 1 to 10 kept in pace with the payload).
Of course you could also do a substring, encoding, etc. it doesn't have to be exactly the same.
Let's say i am starting a new web app with express, and have vscode as my IDE for this nodejs project
Following code is working well with IntelliSense:
Once the handler is extracted, IntelliSense is gone:
I've tried jsdoc:
/**
*
* #param {express.Request} req
* #param {express.Response} res
*/
function test(req, res) {
req. // no luck
}
Is there any way to have IntelliSense supported in this case?
As of VS Code 1.20, this is a limitation when using require with JSDoc types (see this issue]
Workaround is to use import:
import * as express from 'express'
/**
*
* #param {express.Request} req
* #param {express.Response} res
*/
function test(req, res) {
req.
}
https://github.com/Microsoft/TypeScript/issues/14377 also tracks allowing you to specify module imports in jsdocs directly.
in the Docs
http://symfony.com/doc/master/cookbook/profiler/storage.html
you still can find Information about Profiler Storage.
I just checked the code and could not find any clues how to set a custom storage.
I also find no Documentation stating this except some #legacy notes in the Original Source at 2.8.
Is there a Reason why this was removed?
I was using redis to store this data with a lifetime of eta 1hour.
Now I need to run a manual cleanup to whipe all files in that directory.
If anybody has some clues or hints on helping me with this issue are appreceated ^^
Chris
Thanks to the Tip of Matteo I was able to solve this quite flexible.
The Team of Symfony removed this, because it was hard coded into the Profiler Subsystem.
Instead of fixing this by adding a class parameter I had to solve it. :)
Ok, here is the code, If somebody needs this to.
First of all we need the Original Classes from Symfony 2.7 (at least I reused them as I only need the Redis Option ( I use it, because I can Compress the data using igbinary)
Next you need to implement a Compiler Pass.
namespace AcmeBunlde\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ProfilerCompilerPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*
* #param ContainerBuilder $container
*/
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('profiler');
$definition->addArgument('%acmebundle.profiler.defaultEnabled%');
$definition->addArgument('%acmebundle.profiler.class%');
$definition->addArgument('%acmebundle.profiler.dsn%');
$definition->addArgument('%acmebundle.profiler.username%');
$definition->addArgument('%acmebundle.profiler.password%');
$definition->addArgument('%acmebundle.profiler.ttl%');
$definition->setClass('acmebundle\Profiler\Profiler');
}
}
This needs to be loaded inside the Bundle Loader:
public function build(ContainerBuilder $container)
{
...
$container->addCompilerPass(new ProfilerCompilerPass());
}
After this we need to add the Configuration for the New Profiler Storage in the DependencyInjection Folder.
namespace AcmeBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
* #author Chris
*/
class Configuration implements ConfigurationInterface
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('library');
$rootNode
->children()
->arrayNode('profiler')
->addDefaultsIfNotSet()
->children()
->booleanNode('defaultStorage')
->defaultTrue()
->end()
->scalarNode('class')
->defaultValue('')
->end()
->scalarNode('dsn')
->defaultValue('')
->end()
->scalarNode('username')
->defaultValue('')
->end()
->scalarNode('password')
->defaultValue('')
->end()
->scalarNode('ttl')
->defaultValue('3600')
->end()
->end()
->end();
return $treeBuilder();
}
}
Now set the Default Values in The Dependency Injection Bundle Loader
<?php
namespace AcmeBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
* #author Chris
*/
class AcmeExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
...
$container->setParameter('acmebundle.profiler.defaultEnabled',$config['profiler']['defaultStorage']);
$container->setParameter('acmebundle.profiler.class',$config['profiler']['class']);
$container->setParameter('acmebundle.profiler.dsn',$config['profiler']['dsn']);
$container->setParameter('acmebundle.profiler.username',$config['profiler']['username']);
$container->setParameter('acmebundle.profiler.password',$config['profiler']['password']);
$container->setParameter('acmebundle.profiler.ttl',$config['profiler']['ttl']);
...
}
...
}
As Last Step you need to build a basic container for adding the new Profiler Handler.
I have choosen to implement it not to complex:
<?php
namespace AcmeBundle\Profiler;
use Psr\Log\LoggerInterface;
use \Symfony\Component\HttpKernel\Profiler\Profiler as ProfilerSrc;
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
/**
* Profiler.
*/
class Profiler extends ProfilerSrc
{
public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger, $defaultEnabled=true,$class=null,$dsn=null,$username=null,$password=null,$ttl=3600)
{
if($defaultEnabled!==true)
{
$storage = new $class($dsn,$username,$password,$ttl);
}
parent::__construct($storage , $logger);
}
}
I have also added a Library to define the Constructor of the Storage Interface.
<?php
namespace AcmeBundle\Profiler;
use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface as ProfilerStorageSource;
interface ProfilerStorageInterface extends ProfilerStorageSource
{
/**
* ProfilerStorageInterface constructor.
*
* #param $dsn
* #param $username
* #param $password
* #param $ttl
*/
public function __construct($dsn,$username,$password,$ttl);
}
All you need to do now is to define some Options in your config_dev.yml file.
acmebundle:
profiler:
defaultEnabled: false
class:CLASSNAME INCLUDING NAMESPACE
dsn: redis://localhost/1
username:
password
ttl: 3600
with defaultEnabled = true you can reenable to Original Handler.
the rest is, I believe self explaining.
username + password is from the original feature set.
(ttl == lifetime)
I hope this helps somebody else as well :)
Is marked as deprecated since 2.8 with the suppression in the 3.0. I can't find any motivation about in the PR. The doc is not yet updated as you mention.
The only suggestion is about a comment in this issue:
If you want to use your own implementation of a profiler storage,
then just override the profile.storage service.
Hope this help
Has anyone tried or found an example of a class derived from CModel that replicates CActiveRecord functionality with WebServices instead of database connection???
If done with RESTFULL WebServices it would be great. If data is transmitted JSON encoded, wonderful!!...
I'd appretiate your help. Thanks.
I spend a lot of time looking for that as well, I came across this Yii extension on Github:
https://github.com/Haensel/ActiveResource
It allows you to have exactly what you are looking for, the readme isn't updated with the changes reflected in changes.md, so I recommend you read through this document as well.
EActiveResource for Yii
...is an extension for the Yii PHP framework allowing the user to create models that use RESTful services as persistent storage.
The implementation is inspired by Yii's CActiveRecord class and the Ruby on Rails implementation of ActiveResource (http://api.rubyonrails.org/classes/ActiveResource/Base.html).
HINT:
CAUTION: THIS IS STILL AN ALPHA RELEASE!
This project started as a draft and is still under development, so as long is there is no 1.0 release you may experience changes that could break your code. Look at the CHANGES.md file for further information
As there are thousands of different REST services out there that use a thousand different approaches it can be tricky to debug errors. Because of that I added extensive
tracing to all major functions, so you should always be able to see every request, which method it used and how the service responded. Just enable the tracing functionality of Yii
and look for the category "ext.EActiveResource"
INSTALL:
Add the extension to Yii by placing it in your application's extension folder (for example '/protected/extensions')
Edit your applications main.php config file and add 'application.extensions.EActiveResource.*' to your import definitions
Add the configuration for your resources to the main config
'activeresource'=>array(
'class'=>'EActiveResourceConnection',
'site'=>'http://api.aRESTservice.com',
'contentType'=>'application/json',
'acceptType'=>'application/json',
)),
'queryCacheId'=>'SomeCacheComponent')
4.) Now create a class extending EActiveResource like this (don't forget the model() function!):
QUICK OVERVIEW:
class Person extends EActiveResource
{
/* The id that uniquely identifies a person. This attribute is not defined as a property
* because we don't want to send it back to the service like a name, surname or gender etc.
*/
public $id;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function rest()
{
return CMap::mergeArray(
parent::rest(),
array(
'resource'=>'people',
)
);
}
/* Let's define some properties and their datatypes
public function properties()
{
return array(
'name'=>array('type'=>'string'),
'surname'=>array('type'=>'string'),
'gender'=>array('type'=>'string'),
'age'=>array('type'=>'integer'),
'married'=>array('type'=>'boolean'),
'salary'=>array('type'=>'double'),
);
}
/* Define rules as usual */
public function rules()
{
return array(
array('name,surname,gender,age,married,salary','safe'),
array('age','numerical','integerOnly'=>true),
array('married','boolean'),
array('salary','numerical')
);
}
/* Add some custom labels for forms etc. */
public function attributeLabels()
{
return array(
'name'=>'First name',
'surname'=>'Last name',
'salary'=>'Your monthly salary',
);
}
}
Usage:
/* sends GET to http://api.example.com/person/1 and populates a single Person model*/
$person=Person::model()->findById(1);
/* sends GET to http://api.example.com/person and populates Person models with the response */
$persons=Person::model()->findAll();
/* create a resource
$person=new Person;
$person->name='A name';
$person->age=21;
$person->save(); //New resource, send POST request. Returns false if the model doesn't validate
/* Updating a resource (sending a PUT request)
$person=Person::model()->findById(1);
$person->name='Another name';
$person->save(); //Not at new resource, update it. Returns false if the model doesn't validate
//or short version
Person::model()->updateById(1,array('name'=>'Another name'));
/* DELETE a resource
$person=Person::model()->findById(1);
$person->destroy(); //DELETE to http://api.example.com/person/1
//or short version
Person::model()->deleteById(1);
Hope this helps you
I'm learning Kohana at the mo and encountering the following error when trying to extend a model to use ORM.
Declaration of Model_Message::create() should be compatible with that of Kohana_ORM::create()
I've enabled the orm in my bootstrap along with the database. The error highlights the following line on the error dump.
class Model_Message extends ORM {
And here is the model code I'm using and failing with...
<?php defined('SYSPATH') or die('No direct script access');
/**
* Message modal
* Handles CRUD for user messages
*/
class Model_Message extends ORM {
/**
* Adds a new message for a user
*
* #param int user_id
* #param string user's message
* #return self
*/
public function create($user_id, $content)
{
$this->clear();
$this->user_id = $user_id;
$this->content = $content;
$this->date_published = time();
return $this->save();
}
}
I've been going through the api documentation and everything is saying that this way of implementing the orm from the model is the correct way to do so. Any pointers would be great.
You need to rename your method (create_message for example) or make it compatible with ORM (because it has the it's own create method which you try to override).