make field optional but let it depend on other fields if set - class-validator

I have a DTO filtering locations. To query just some locations I can provide a latitude, longitude and radius. All three of them are optional fields but when I set one of them it requires the other two too. So what I have so far
export class GetLocationsDTO {
#IsNumber()
#IsLatitude()
// optional but requires longitude and radius
#Type(() => Number)
public latitude?: number;
#IsNumber()
#IsLongitude()
// optional but requires latitude and radius
#Type(() => Number)
public longitude?: number;
#IsNumber()
// optional but requires latitude and longitude
#Type(() => Number)
public radiusInKilometers?: number;
}
Is there a decorator like this sample
#IsOptionalButDependsOn(['fieldFoo', 'fieldBar'])
So all three of them are optional but if one of them was provided the other two fields have to be provided too.

I guess you need to add ValidateIf.
export class GetLocationsDTO {
#IsNumber()
#IsLatitude()
#ValidateIf(o => o.radiusInKilometers !== undefined || o.longitude !== undefined)
#Type(() => Number)
public latitude?: number;
#IsNumber()
#IsLongitude()
#ValidateIf(o => o.latitude !== undefined || o.radiusInKilometers !== undefined)
#Type(() => Number)
public longitude?: number;
#IsNumber()
#ValidateIf(o => o.latitude !== undefined || o.longitude !== undefined)
#Type(() => Number)
public radiusInKilometers?: number;
}

Related

Can't get registered user.id in AdonisJS when setting relationships in controller

My code:
users migration
public async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id').primary()
table.string('password', 180).nullable()
table.timestamp('created_at', {useTz: true}).notNullable()
table.timestamp('updated_at', {useTz: true}).notNullable()
}
words migration
public async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.timestamp('created_at', {useTz: true})
table.timestamp('updated_at', {useTz: true})
table.integer('user_id').unsigned().references('users.id').onDelete('CASCADE')
})
}
Models/User
#hasMany(() => Word)
public words: HasMany<typeof Word>
Models/Word
#column()
public userId: number
#belongsTo(() => User)
public author: BelongsTo<typeof User>
WordsController
public async store({request, auth}: HttpContextContract) {
const user = auth.user!.id
const data = await request.validate(WordValidator)
const word = await user?.related('words').create(data)
return word
}
When I type auth.user!.id as it is in upper example, it returns this:
"Cannot read properties of undefined (reading 'id')"
I'm registered, it even normally shows my id on other requests (but only get requests).
What can I do, to get registered user id, and set it as userId in word table or what am I doing wrong?
Thanks

How to allow null, but forbid undefined?

e.g. for database rows, we may need nullable properties that must not be undefined:
class DbRow {
#IsNumber()
id!: number;
#IsNumber()
numNullable!: number | null;
}
So numNullable can be a number or null - but it must never be undefined.
How can we express this in class-validator?
adding #Optional() does not work, because that would also allow undefined
I also had no luck with a custom validator
It turns out that this is possible by using conditional validation ValidateIf:
class DbRow {
#IsNumber()
id!: number;
#IsNumber()
#ValidateIf((object, value) => value !== null)
numNullable!: number | null;
}
Here is a stackblitz example
Here is my solution:
import { ValidationOptions, ValidateIf } from 'class-validator';
export function IsNullable(validationOptions?: ValidationOptions) {
return ValidateIf((_object, value) => value !== null, validationOptions);
}
Usage
import { plainToClass } from 'class-transformer';
import { IsNumber, validateSync } from 'class-validator';
import { IsNullable } from 'src/common/utils/is-nullable.decorator';
class SampleDto {
#IsNullable()
#IsNumber()
foo: number | null;
}
describe('IsNullable', () => {
it('should disable other validators when given property is null', () => {
expect(validateSync(plainToClass(SampleDto, { foo: null }))).toEqual([]);
});
it('should allow other validators to work when given property is not null', () => {
expect(validateSync(plainToClass(SampleDto, { foo: 1 }))).toEqual([]);
expect(validateSync(plainToClass(SampleDto, { foo: '1' }))[0].constraints.isNumber).toMatch('foo must be a number');
});
it('should not allow undefined', () => {
expect(validateSync(plainToClass(SampleDto, { foo: undefined })).length).toBeGreaterThan(0);
});
});
This is an extended version of IsOptional exported from class-validator.
import {
ValidationOptions,
ValidateIf,
IsOptional as IsOptionalValidator,
} from 'class-validator';
/**
* Checks if value is missing and if so, ignores all validators.
*
* #param nullable If `true`, all other validators will be skipped even when the value is `null`. `false` by default.
* #param validationOptions {#link ValidationOptions}
*
* #see IsOptional exported from `class-validator.
*/
export function IsOptional(
nullable = false,
validationOptions?: ValidationOptions,
) {
if (nullable) {
return IsOptionalValidator(validationOptions);
}
return ValidateIf((ob: any, v: any) => {
return v !== undefined;
}, validationOptions);
}
That's the limitation of the library, it doesn't allow condition branching.
The best way is to write your own validator that allows only nulls.

Facebook Workplace Account Management API Not Returning Photos

I am having an issue with the Account Management API for Facebook Workplace. All I am trying to do is build a quick and easy employee directory, that grabs all of our active users and spits out their name, title, dept, and photos. The problem is, the data coming back does not seem to match the Facebook Core Schema as seen in the link above. Some of the schema data comes back, but never photos, no matter what I seem to try.
private function getEmployees()
{
$done = false;
$current_index = 1;
$current_page = 1;
$results = [];
while(!$done) {
$res = $this->client->request(
'GET',
'https://www.facebook.com/company/XXXXXXXXX/scim/Users?count=100&startIndex=' . $current_index,
[
'headers' => ['Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->token
]
]
);
$decoded = json_decode($res->getBody());
$total = $decoded->totalResults;
$perPage = $decoded->itemsPerPage;
if (isset($decoded->Resources)) {
$results = array_merge($results, $decoded->Resources);
if (($current_page * $perPage) >= $total) {
$done = true;
} else {
$current_page++;
$current_index += $perPage;
}
} else {
$done = true;
}
}
return $results;
}
Which gives back:
object(stdClass)[392]
public 'schemas' =>
array (size=3)
0 => string 'urn:scim:schemas:core:1.0' (length=25)
1 => string 'urn:scim:schemas:extension:enterprise:1.0' (length=41)
2 => string 'urn:scim:schemas:extension:facebook:starttermdates:1.0' (length=54)
public 'id' => int 10001156699923
public 'userName' => string 'np#lt.com' (length=21)
public 'name' =>
object(stdClass)[393]
public 'formatted' => string 'Nick P' (length=11)
public 'title' => string 'Lead PHP Engineer' (length=17)
public 'active' => boolean true
public 'phoneNumbers' =>
array (size=1)
0 =>
object(stdClass)[394]
public 'primary' => boolean true
public 'type' => string 'work' (length=4)
public 'value' => string '+1631123456' (length=12)
public 'addresses' =>
array (size=1)
0 =>
object(stdClass)[395]
public 'type' => string 'attributes' (length=10)
public 'formatted' => string 'Manhattan' (length=9)
public 'primary' => boolean true
public 'urn:scim:schemas:extension:enterprise:1.0' =>
object(stdClass)[396]
public 'department' => string 'IT' (length=2)
public 'manager' =>
object(stdClass)[397]
public 'managerId' => int 100011017901494
public 'urn:scim:schemas:extension:facebook:starttermdates:1.0' =>
object(stdClass)[398]
public 'startDate' => int 0
public 'termDate' => int 0
So as you can see, it returns other fields that are part of the 'core' schema, but is missing the 'photos' array and others. I thought this might have been because a user didnt have any photos, but almost all have profile pictures, and many have more. I tried getting their user information specifically but encountered the same result, no photos.
Anybody ever try something similar? Any help much appreciated, this has been a bit of a road block for us.
Thanks
To get profile information, don't use SCIM but graph API
https://graph.facebook.com/community/members will list all members
and https://graph.facebook.com/[email] for one of your member will get all infos.
After that you have to set the params you want to get with the fields param.
In our implementation we get the whole data from this request
https://graph.facebook.com/XXXX/members?fields=email,picture.type(large),link,title,first_name,last_name,department,updated_time,managers{email}&limit=500

Moq + Unit Testing + System.Reflection.TargetParameterCountException: Parameter Count mismatch

I am getting a "System.Reflection.TargetParameterCountException: Parameter Count mismatch" exception when attempting to mock our ApiClient.
I am using the following code to Setup the Moq response
private void SetupApiClientForGetZones(IEnumerable<Zone> zone)
{
this.MockHubApiClient.Setup(x => x.GetAsync<IEnumerable<Zone>>(It.IsAny<string>(), It.IsAny<IDictionary<string, string>>()))
.Returns(
(string name) =>
{
return zone == null ? Task.FromResult<IEnumerable<Zone>>(null) : Task.Run(() => zone);
});
this.MockApiClientFactory.Setup(x => x.CreateClient(It.IsAny<string>()))
.Returns(this.MockHubApiClient.Object);
}
The iApiClient interface I attempting to Mock is
public interface IApiClientAsync : IApiClient
{
Task<string> GetAsync(string apiController);
Task<T> GetAsync<T>(string apiController) where T : class;
Task<string> GetAsync(string apiController, IDictionary<string, string> param, string queryString);
Task<T> GetAsync<T>(string apiController, IDictionary<string, string> param) where T : class;
}
My unit test is
[Test]
public void GetZonesNotCached()
{
var data = new List<Zone> { new Zone { ZoneId = 1, ZoneName = "Test Zone 1" }, new Zone { ZoneId = 2, ZoneName = "Test Zone 2" } };
this.SetupApiClientForGetZones(data);
this.MockCache.Setup(x => x.GetItemFromCache<IEnumerable<Zone>>(It.IsAny<string>()));
var organisationService = new OrganisationService(this.MockUnitOfWorkAsync.Object, this.MockApiClientFactory.Object, this.MockCache.Object);
var results = organisationService.GetZones(1, 1).ToList();
Assert.IsNotNull(results);
Assert.AreEqual(3, results.Count, "There should be 3 records returned");
this.MockCache.Verify(x => x.GetItemFromCache<IEnumerable<Zone>>(It.IsAny<string>()), Times.Once());
this.MockHubApiClient.Verify(x => x.GetAsync<IEnumerable<Zone>>(It.IsAny<string>(), It.IsAny<IDictionary<string, string>>()), Times.Once());
}
I have found numerous other posts with the same exception but none of the solutions or examples are the same as mine.
I have been able to successfully Mock the response when calling the GetAsync method that only has the single string paramter.
private void SetupApiClientForAllDealerDetails(IEnumerable<DealerDetail> dealerDetails)
{
this.MockHubApiClient.Setup(
x => x.GetAsync<IEnumerable<DealerDetail>>(It.IsAny<string>()))
.Returns(
(string name) =>
{
return dealerDetails == null ? Task.FromResult<IEnumerable<DealerDetail>>(null) : Task.Run(() => dealerDetails);
});
this.MockApiClientFactory.Setup(x => x.CreateClient(It.IsAny<string>()))
.Returns(this.MockHubApiClient.Object);
}
Any ideas anyone?
If you use an expression in your .Returns instead of a value, then that expression's parameters must match those of the method signature you are mocking.
For example, I found this in SetupApiClientForGetZones:
this.MockHubApiClient.Setup(x => x.GetAsync<IEnumerable<Zone>>(It.IsAny<string>(), It.IsAny<IDictionary<string, string>>()))
.Returns(
(string name) =>
{
return zone == null ? Task.FromResult<IEnumerable<Zone>>(null) : Task.Run(() => zone);
});
When really it should be:
this.MockHubApiClient.Setup(x => x.GetAsync<IEnumerable<Zone>>(It.IsAny<string>(), It.IsAny<IDictionary<string, string>>()))
.Returns<string, IDictionary<string, string>>(
(name, dict) =>
{
return zone == null ? Task.FromResult<IEnumerable<Zone>>(null) : Task.Run(() => zone);
});

using Moq - question

i have just started using Moq ver (3.1) and i have read blogs and what not.... anyway... i guess until you makes your hand dirty you will not learn :)
okay here is what i'm testing...
var newProduct = new Mock<IPartialPerson>();
newProduct.SetupGet(p => p.FirstName).Returns("FirstName");
newProduct.SetupGet(p => p.MiddleName).Returns("MiddleName");
newProduct.SetupGet(p => p.LastName).Returns("LastName");
newProduct.SetupGet(p => p.EmailAddress).Returns("EmailAddress#hotmail.com");
newProduct.SetupGet(p => p.UserID).Returns("UserID");
//mock Escort repository
var mockEscortRepository = new Mock<IEscortRepository>();
mockEscortRepository.Setup(p => p.LoadAllEscorts())
.Returns(newProduct.Object); //error
Error 1 The best overloaded method
match for
'Moq.Language.IReturns>.Returns(System.Collections.Generic.List)'
has some invalid arguments
Error 2 Argument '1': cannot convert
from
'App.Model.Interface.IPartialPerson'
to
'System.Collections.Generic.List'
public interface IPartialPerson
{
string FirstName { get; }
string MiddleName { get; }
string LastName { get; }
string EmailAddress { get; }
string FullName { get; }
string UserID { get; }
}
public interface IEscortRepository
{
List<PartialPerson> LoadAllEscorts();
List<PartialPerson> SelectedEscorts(List<PartialPerson> selectedEscorts);
}
i have two methods that i want to test "LoadAllaEscorts" and "SelectedEscorts"
how would i do a test for both methods?
Try this:
mockEscortRepository
.Setup(p => p.LoadAllEscorts())
.Returns(new List<IPartialPerson>() { newProduct.Object } );
When you say:
.Returns(newProduct.Object)
You are asking Moq to return one specific object. The compiler sees that your method returns a list and so it will not compile unless you provide a list for it to return. So you need to create a list that contains the object you want to return then ask Moq to return that instead.
Based on your comments you might also be interested in testing a list with more than one item. To do that, create a list with more than one item, then ask the Mock to return that. Here is one way you could create a list with more than one item:
List<PartialPerson> escorts = new List<PartialPerson>();
for (int i = 0; i < 10; i++)
{
var escort = new Mock<IPartialPerson>();
escort.SetupGet(p => p.FirstName).Returns("FirstName" + i);
escort.SetupGet(p => p.MiddleName).Returns("MiddleName" + i);
escort.SetupGet(p => p.LastName).Returns("LastName" + i);
escort.SetupGet(p => p.EmailAddress).Returns(i + "EmailAddress#hotmail.com");
escort.SetupGet(p => p.UserID).Returns("UserID" + i);
escorts.Add(escort.Object);
}
mockEscortRepository
.Setup(p => p.LoadAllEscorts())
.Returns(escorts);
Good luck and keep on pimpin!
i have two methods that i want to test
"LoadAllaEscorts" and
"SelectedEscorts"
Those are methods on an interface. You don't write tests against an interface, or against mock objects. You write tests against concrete classes.
Somewhere you have an EscortRepository that implements IEscortRepository. I'm assuming that hits the database. Write integration tests against that.
Elsewhere in your code you probably have a class (call it "Foo") that has an IEscortRepository dependency injected into it (such as via a constructor parameter). When you want to write tests against the Foo class, you would use Moq to create a mock IEscortRepository returning fixed test data and pass that mock object into your Foo instance.
Another issue is that your IEscortRepository methods are returning (or taking as a parameter) List<PartialPerson>. Those should be IList<IPartialPerson> (or IEnumerable<T>, ICollection<T>, or ReadOnlyCollection<T>). The most important part is that the collection items should be an interface type (IPartialPerson).
+1 for magnifico, who had the code right:
using System;
using System.Collections.Generic;
using Moq;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
var newProduct = new Mock<IPartialPerson>();
newProduct.SetupGet(p => p.FirstName).Returns("FirstName");
newProduct.SetupGet(p => p.MiddleName).Returns("MiddleName");
newProduct.SetupGet(p => p.LastName).Returns("LastName");
newProduct.SetupGet(p => p.EmailAddress).Returns("EmailAddress#hotmail.com");
newProduct.SetupGet(p => p.UserID).Returns("UserID");
var mockEscortRepository = new Mock<IEscortRepository>();
mockEscortRepository
.Setup(p => p.LoadAllEscorts())
.Returns(new List<IPartialPerson> {newProduct.Object});
IEscortRepository repository = mockEscortRepository.Object;
IList<IPartialPerson> escorts = repository.LoadAllEscorts();
foreach (IPartialPerson person in escorts)
{
Console.WriteLine(person.FirstName + " " + person.LastName);
}
Console.ReadLine();
// Outputs "FirstName LastName"
}
}
public interface IPartialPerson
{
string FirstName { get; }
string MiddleName { get; }
string LastName { get; }
string EmailAddress { get; }
string FullName { get; }
string UserID { get; }
}
public interface IEscortRepository
{
IList<IPartialPerson> LoadAllEscorts();
IList<IPartialPerson> SelectedEscorts(IList<IPartialPerson> selectedEscorts);
}
}
(The above example is not a unit test; it just shows that Moq works.)
Note that you don't have to use SetupGet for properties; Setup works as well.
Your mock is setup to return a single item, and it should return a List according to the repository interface.