How can I create a KOA path with a number? - koa

I have an KOA endpoint. I have a quantify param that can only accept numbers, how can I enforce this directly in the KOA router?
.put('/cart/:product/:quantity', async ctx => {
quantity = ctx.params.quantity;
ctx.body = 'my code here';
}

Use this regexp:
'/cart/:product/:quantity(\\d+)'
^ matches quantities that only consist of numbers. \d+ is the regexp, but you have to add another \ for the router to convert it into a proper regexp since the route is a string.

Related

Wrong Cypress intercept being hit

I have set up a number of intercepts in the body of my tests. Here they are, pasted from the Cypress log, with the alias added
cy:intercept ➟ // alias: getRecipesSku(971520)
Method: GET
Matcher: "https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&_fields**"
Mocked Response: [{ ... }]
cy:intercept ➟ // alias: getRecipesSku(971520,971310)
Method: GET
Matcher: "https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&tags[]=6289&_fields**"
Mocked Response: [{ ... }]
Our application's tests also mocks a number of routes by default, (coming from an apiClient.initialize) including this one below. FWIW, this is defined earlier than those above:
cy:intercept ➟ // alias: getJustcookRecipes
Method: GET
Matcher: "https://wpsite.com/wp-json/**"
Mocked Response: [{ ... }]
My test code sets up those first two intercepts, and ultimately calls the routes. Here is the code, heavily abridged:
it('refreshes the recipes when switching protein tabs', () => {
apiClient.initialize()
/* do lots of other stuff; load up the page, perform other tests, etc */
// call a function that sets up the intercepts. you can see from the cypress output
// that the intercepts are created correctly, so I don't feel I need to include the code here.
interceptJustCook({ skus: [beefCuts[0].id] }, [beefCut1Recipe])
interceptJustCook({ skus: [beefCuts[0].id, beefCuts[1].id] }, twoBeefRecipes)
// [#1] select 1 item;
// calls route associated with intercept "getRecipesSku(971520)"
page.click.checkboxWithSku(beefCuts[0].id)
/* assert against that call */
// [#2] select 2nd item (now 2 items are selected);
// calls route associated with intercept "getRecipesSku(971520, 971310)"
page.click.checkboxWithSku(beefCuts[1].id)
In the Cypress logs, the first call (marked by comment #1) is intercepted correctly:
cy:fetch ➟ (getRecipesSku(971520)) STUBBED
GET https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&_fields=jetpack_featured_media_url,title.rendered,excerpt.rendered,link,id&per_page=100&page=1&orderby=date
However, the second call (marked by comment #2) is intercepted by the wrong route mocker:
cy:fetch ➟ (getJustCookRecipes) STUBBED
GET https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&tags[]=6289&_fields=jetpack_featured_media_url,title.rendered,excerpt.rendered,link,id&per_page=100&page=1&orderby=date
You can see for yourself that the URL called at #2 does indeed match the getRecipesSku(971520, 971310) intercept, but it is caught by the getJustcookRecipes intercept. Now, I suppose the URL for that latter intercept would catch my second custom intercept. But it would also, in the same way, catch my first custom intercept, but that first one works.
(update:) I tried commenting out the place in the code where the getJustcookRecipes intercept is created so that it doesn't exist. Now, the call that should hit getRecipesSku(971520,971310) isn't being mocked at all! I checked and the mocked and called urls are a match.
Why is this going wrong and how do I fix it?
Something in the glob pattern for the 2nd intercept #getRecipesSku(971520,971310) is refusing to match.
It's probably not worth while analyzing what exactly (you may not be able to fix it in glob), but switching to a regex will match.
See regex101.com online test
cy.intercept(/wpsite\.com\/wp-json\/wp\/v2\/posts\?tags\[]=6287&tags\[]=6289&_fields/, {})
.as('getRecipesSku(971520,971310)')
The request query string may be badly formed
Looking at the docs for URLSearchParams, the implication is that the query string should be key/value pairs.
But the 2nd request has two identical keys using tags[] as the key.
It looks as if the correct format would be /wp/v2/posts?tags=[6287,6289] since the square brackets don't have a lot of meaning otherwise.
It may be that the server is handling the format tags[]=6287&tags[]=6289, but Cypress is probably not. If you run the following intercept you see the query object has only one tags[] key and it's the last one in the URL.
cy.intercept({
method: 'GET',
pathname: '/wp-json/wp/v2/posts',
},
(req) => {
console.log('url', req.url)
console.log('query', req.query) // Proxy {tags[]: '6289', ...
}
)
#Paolo was definitely on the right track. The WP API we're consuming uses tags[]=1,tags=[2] instead of tags[1,2] so that's correct, but some research into globs showed me that brackets are special. Why the first bracket isn't messing it up but the second one is, I'm not sure and I kind of don't care.
I thought about switching to regex, but instead I was able to escape the offender in the glob without switching to regex and needing to escape aaaallll the slashes in the url:
const interceptRecipes = (
{ category, skus }: RecipeFilterArgs,
recipes: Recipe[]
) => {
let queryString = ''
if (category) queryString = `categories[]=${CATEGORY_MAP[category]}`
if (skus) {
const tagIdMap = SkuToTagIdMap as Record<number, { tagId: number }>
// brackets break the glob pattern, so we need to escape them
queryString = skus.map((sku) => `tags\\[]=${tagIdMap[sku].tagId}`).join('&')
}
const url = `${baseUrl}?${queryString}${otherParams}`
const alias = category ? `get${category}Recipes` : `getRecipesSku(${skus})`
cy.intercept('GET', url, recipes).as(alias)
}

Cypress intercept with not condition

I have the following service calls on click of an element. I will need to intercept a request that does not have f0000000000 in the endpoint.
https://example.com/f0000000000/path1 - GET
https://example.com/f0000000000/path1 - GET
https://example.com/f0000000000/path1 - GET
https://example.com/f0000000000/path1 - GET
https://example.com/f0000000000/path1 - GET
https://example.com/f0000000000/path1 - GET
https://example.com/f2021090606/path1 - GET
How can we achieve this in cypress? Do we have any similar options?
cy.intercept("GET", "**/NOT(f0000000000)/path1*").as('getForecast');
cy.get('#some').click();
cy.wait('#getForecast').its('response.statusCode').should('eq', 200)
});
With regex
You wrap the part to exclude in a negative lookahead group,
(?!NOT-THE-TEXT-YOU-ARE-LOOKING-FOR)
but also must add wildcard for any other text in that place after the group, i.e .*
((?!NOT-THE-TEXT-YOU-ARE-LOOKING-FOR).*)
Shortest
Ensure any domain, but must have /path1 and not have f0000000000
const regex = /((?!f0000000000).*)\/path1/
cy.intercept(regex, {}).as('intercept')
cy.wait('#intercept')
More exact
Specify the domain more exactly
cy.intercept(/^https:\/\/example\.com\/((?!f0000000000).*)\/path1/, {}).as('intercept')
cy.wait('#intercept')
Specify digits more exactly
If you always have f prefix and just want the digits wilcarded, move the f outside the exclusion.
Optionally replace .* with [0-9]{10} to specify exactly 10 digits not matching 0000000000
cy.intercept(/^https:\/\/example\.com\/f((?!0000000000)[0-9]*)\/path1/, {}).as('intercept')
cy.wait('#intercept')
With minimatch
Just change NOT(f0000000000) to !(f0000000000)
const url1 = 'https://example.com/f2021090606/path1x'
const match1 = Cypress.minimatch(url1, '**/!(f0000000000)/path1*')
expect(match1).to.eq(true)
Conversly
const url2 = 'https://example.com/f0000000000/path1x'
const match2 = Cypress.minimatch(url2, '**/!(f0000000000)/path1*')
expect(match2).to.eq(false)
Intercept
cy.intercept('**/!(f0000000000)/path1*', {}).as('intercept')
//or
cy.intercept('**/f!(0000000000)/path1*', {}).as('intercept')
But be careful with trailing path parts
const url = 'https://example.com/f0000000001/path1/x'
const match = Cypress.minimatch(url, '**/!(f0000000000)/path1/*')
expect(match).to.eq(true)
You can create a regex for this:
cy.intercept(/^(?!.*f0000000000\/path1).*$/gm).as('getForecast')
This will intercept the url which doesn't have f0000000000. This is a very basic regex, you can always enhance it as per your needs.
You can also look into the Intercept Cypress Recipe.

Cleanest way to inject into string

We are looking to optimize images with a thumbnail version, which are stored under a funky version of the existing URL:
Original Image:
https://image.s3-us-west-2.amazonaws.com/8/flower.jpg
Thumbnail Image:
https://image.s3-us-west-2.amazonaws.com/8/thumbnails/medium_flower.jpg
I was going to look from the end of the string for the last '/' and replacing it with '/thumbnails/medium_'. In my case this always safe, but I can't figure out this kind of mutation in Ruby on Rails.
s = "https://image.s3-us-west-2.amazonaws.com/8/flower.jpg"
img_url = s.split('/')[-1] // should give 'flower.jpg'
The issue is to get everything before the last '/' to inject in 'thumbnails/medium_'. Any ideas?
s = "https://image.s3-us-west-2.amazonaws.com/8/flower.jpg"
img_url = s.insert(s.rindex('/')+1, 'thumbnails/medium_')
# The above approach modifies the original string, if this is unsatisfactory, use:
img_url = s.dup.insert(s.rindex('/')+1, 'thumbnails/medium_')
s = "https://image.s3-us-west-2.amazonaws.com/8/flower.jpg"
img_url = "#{File.dirname(s)}/thumbnails/medium_#{File.basename(s)}"
# => "https://image.s3-us-west-2.amazonaws.com/8/thumbnails/medium_flower.jpg"
I would probably use URI and Pathname to work with URLs and file paths:
require 'uri'
require 'pathname'
url = "https://image.s3-us-west-2.amazonaws.com/8/flower.jpg"
uri = URI.new(url)
path = Pathname.new(uri.path)
uri.path = "#{path.dirname}/thumbnails/medium_#{path.basename}"
uri.to_s
#=> "https://image.s3-us-west-2.amazonaws.com/8/thumbnails/medium_flower.jpg"
s = "https://image.s3-us-west-2.amazonaws.com/8/flower.jpg"
s.sub /([^\/]+)$/, 'thumbnails/medium_\1'
The s.sub's 2nd argument should be quoted with single quotation mark, or you have to escape the backslash in the \1 part.
UPDATE
s.sub /([^\/]+?)(?=$|\?|#)$/, 'thumbnails/medium_\1'
In case there's a query string or a fragment or both, behind the path, which contains slashes.
It's #[Range] method what you need:
# a little performance optimization - no need to split split string twice
parts = s.split('/')
img_url = parts[0..-2].join('/') + "/thumbnails/medium_" + parts[-1]
On a side note. If you are using some Rails plugin for handling images (CarrierWave or Paperclip), you should use built-in mechanisms for URL interpolation.

Insert a string into an URL after a specific character

I have a URL string:
http://ip-address/user/reset/1/1379631719drush/owad_yta75
into which I need to insert a short string:
help after the third occurance of "/" so that the new string will be:
http://ip-address/**help**/user/reset/1/1379631719drush/owad_yta75
I cannot use Ruby's string.insert(#, 'string') since the IP address is not always the same length.
I'm looking at using a regex to do that, but I am not exactly sure how to find the third '/' occurance.
One way to do this:
url = "http://ip-address/user/reset/1/1379631719drush/owad_yta75"
url.split("/").insert(3, 'help').join('/')
# => "http://ip-address/help/user/reset/1/1379631719drush/owad_yta75"
You can do that with capturing groups using a regex like this:
(.*?//.*?/)(.*)
^ ^ ^- Everything after 3rd slash
| \- domain
\- protocol
Working demo
And use the following replacement:
\1help/\2
If you check the Substitution section you can see your expected output
The thing you're forgetting is that a URL is just a host plus file path to a resource, so you should take advantage of tools designed to work with those. While it'll seem unintuitive at first, in the long run it'll work better.
require 'uri'
url = 'http://ip-address/user/reset/1/1379631719drush/owad_yta75'
uri = URI.parse(url)
path = uri.path # => "/user/reset/1/1379631719drush/owad_yta75"
dirs = path.split('/') # => ["", "user", "reset", "1", "1379631719drush", "owad_yta75"]
uri.path = (dirs[0,1] + ['help'] + dirs[1 .. -1]).join('/')
uri.to_s # => "http://ip-address/help/user/reset/1/1379631719drush/owad_yta75"

How to route 2 parameters to a controller?

This seems really basic but i can't get the hang of it.
I'm trying to send more then one parameter to a method in the controller, like this :
http://localhost/ci/index.php/subjects/3/state
This is the routings i've tried :
$route['subjects/(:num)'] = 'subjects/view/$1';
$route['subjects/(:num)/{:any}'] = 'subjects/view/$1/$2';
the method accepted 2 paremeters :
public function view($slug, $id = null){
}
but i seem to get a 404. How can i get this to work? i need the view method to always accept 1 parameter and optional other parameters.
NOTE : I am including the url helper.
you have problem with your route brackets just change it from {} to () brackets will work
from
$route['subjects/(:num)/{:any}'] = 'subjects/view/$1/$2';
to
$route['subjects/(:num)/(:any)'] = 'subjects/view/$1/$2';
Always maintain your routing rules
like
$route['subjects/(:num)/(:any)/(:any)/(:any)'] = 'subjects/view/$1/$2/$3/$4';
$route['subjects/(:num)/(:any)/(:any)'] = 'subjects/view/$1/$2/$3';
$route['subjects/(:num)/(:any)'] = 'subjects/view/$1/$2';
always follow this pattern for routing
if you add like this
$route['subjects/(:num)/(:any)'] = 'subjects/view/$1/$2';
$route['subjects/(:num)/(:any)/(:any)/(:any)'] = 'subjects/view/$1/$2/$3/$4';
$route['subjects/(:num)/(:any)/(:any)'] = 'subjects/view/$1/$2/$3';
then always first condition will be true every time.
also refer this link --> codeigniter routing rules
I once tried this URI pattern
$route['(:any)'] = 'welcome/list1/$1';
$route['(:any)/(:num)'] = 'welcome/list1/$1/$2';
$route['(:any)/(:any)'] = 'welcome/list2/$1/$2';
$route['(:any)/(:any)/(:num)'] = 'welcome/list2/$1/$2/$3';
$route['(:any)/(:any)/(:any)'] = 'welcome/list3/$1/$2/$3';
but it didnt worked ... so I replaced it with regular expression
([a-z 0-9 -]+) replaced (:any)
and
([0-9]+) replaced (:num)
so it became
$route['([a-z 0-9 -]+)'] = 'welcome/list1/$1';
$route['([a-z 0-9 -]+)/([0-9]+)'] = 'welcome/list1/$1/$2';
$route['([a-z 0-9 -]+)/([a-z 0-9 -]+)'] = 'welcome/list2/$1/$2';
$route['([a-z 0-9 -]+)/([a-z 0-9 -]+)/([0-9]+)'] = 'welcome/list2/$1/$2/$3';
$route['([a-z 0-9 -]+)/([a-z 0-9 -]+)/([a-z 0-9 -]+)'] = 'welcome/list3/$1/$2/$3';
And it worked for me :)
In order to access the variables in your controllers, you can assign any parameter in the function.
class Welcome extends CI_Controller {
public function list($first, $second)
{
var_dump($first);
var_dump($second);
}
}

Resources