Skip to content

Commit

Permalink
Merge pull request #334 from superfaceai/fix/undefined_input
Browse files Browse the repository at this point in the history
Input validation to work with None values
  • Loading branch information
freaz authored Feb 15, 2023
2 parents fc011a0 + 597202e commit be46882
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Correctly handle objects with `null` prototype in `isClassInstance` - [#333](https://github.com/superfaceai/one-sdk-js/pull/333)
- Treat not defined input in profile as nullable data structure - [#334](https://github.com/superfaceai/one-sdk-js/pull/334)

## [2.3.0] - 2023-02-07
### Changed
Expand Down
20 changes: 11 additions & 9 deletions src/core/events/reporter/reporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ describe('MetricReporter', () => {

const profile = await client.getProfile('test-profile');

await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform(undefined);
client.timers.tick(2000);
while (await eventEndpoint.isPending()) {
await new Promise(setImmediate);
Expand Down Expand Up @@ -382,7 +382,9 @@ describe('MetricReporter', () => {

const profile = await client.getProfile('test-profile');

await expect(profile.getUseCase('Test').perform({})).rejects.toThrow();
await expect(
profile.getUseCase('Test').perform(undefined)
).rejects.toThrow();
client.timers.tick(2000);
let requests = await eventEndpoint.getSeenRequests();
while (requests.length < 2) {
Expand Down Expand Up @@ -460,7 +462,7 @@ describe('MetricReporter', () => {

const profile = await client.getProfile('test-profile');

await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform(undefined);
client.timers.tick(800);
while (await eventEndpoint.isPending()) {
await new Promise(setImmediate);
Expand Down Expand Up @@ -496,8 +498,8 @@ describe('MetricReporter', () => {
);
const profile = await client.getProfile('test-profile');

await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform(undefined);
await profile.getUseCase('Test').perform(undefined);
client.timers.tick(2000);
let requests = await eventEndpoint.getSeenRequests();

Expand Down Expand Up @@ -544,14 +546,14 @@ describe('MetricReporter', () => {
);
const profile = await client.getProfile('test-profile');

await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform(undefined);
client.timers.tick(2000);
let requests = await eventEndpoint.getSeenRequests();
while (requests.length < 1) {
await new Promise(setImmediate);
requests = await eventEndpoint.getSeenRequests();
}
await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform(undefined);
client.timers.tick(1000);
while (requests.length < 2) {
await new Promise(setImmediate);
Expand Down Expand Up @@ -618,7 +620,7 @@ describe('MetricReporter', () => {
const profile = await client.getProfile('test-profile');

for (let i = 0; i < 100; i++) {
await profile.getUseCase('Test').perform({});
await profile.getUseCase('Test').perform(undefined);
client.timers.tick(900);
currentTime = currentTime.valueOf() + 900;
}
Expand Down Expand Up @@ -696,7 +698,7 @@ describe('MetricReporter', () => {
);
const profile = await client.getProfile('test-profile');

void profile.getUseCase('Test').perform({});
void profile.getUseCase('Test').perform(undefined);
let requests = await eventEndpoint.getSeenRequests();
while (requests.length < 2) {
currentTime += 0.1;
Expand Down
50 changes: 50 additions & 0 deletions src/core/interpreter/profile-parameter-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ describe('ProfileParameterValidator', () => {
});

it('returns ok for valid input data', () => {
parameterValidator = new ProfileParameterValidator(ast);
expect(
parameterValidator
.validate(
Expand All @@ -874,6 +875,55 @@ describe('ProfileParameterValidator', () => {
.isOk()
).toBeTruthy();
});

it("returns error if input isn't passed", () => {
expect(
parameterValidator.validate(null, 'input', 'Test').isErr()
).toBeTruthy();
});

describe('profile without input defined', () => {
const astNoInput = parseProfileFromSource(`
usecase Test {
}
`);

beforeEach(() => {
parameterValidator = new ProfileParameterValidator(astNoInput);
});

it('returns ok if null passed', () => {
expect(
parameterValidator.validate(null, 'input', 'Test').isOk()
).toBeTruthy();
});

it('returns ok if non primitive value without additional fields is passed', () => {
expect(
parameterValidator.validate({}, 'input', 'Test').isOk()
).toBeTruthy();
});

it('returns error if non primitive value with additional fields is passed', () => {
expect(
parameterValidator
.validate(
{
additionalField: 'value',
},
'input',
'Test'
)
.isErr()
).toBeTruthy();
});

it('returns error if primitive value is passed', () => {
expect(
parameterValidator.validate('string', 'input', 'Test').isErr()
).toBeTruthy();
});
});
});

describe('result validation', () => {
Expand Down
27 changes: 19 additions & 8 deletions src/core/interpreter/profile-parameter-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import type {
ProfileParameterError,
} from '../../interfaces';
import type { Result } from '../../lib';
import { err, isNone, ok, UnexpectedError } from '../../lib';
import { err, isNone, isNonPrimitive, ok, UnexpectedError } from '../../lib';
import type { ProfileVisitor } from './interfaces';
import type { ValidationError } from './profile-parameter-validator.errors';
import {
Expand Down Expand Up @@ -581,20 +581,31 @@ export class ProfileParameterValidator implements ProfileVisitor {
kind: ProfileParameterKind,
usecase: string
): ValidationFunction {
if (kind === 'input' && node.input) {
return addPath(this.visit(node.input.value, kind, usecase), 'input');
if (kind === 'input' && node.input !== undefined) {
return addPath((input): ValidationResult => {
if (isNone(input)) {
return [false, [{ kind: 'nullInNonNullable' }]];
}

if (node.input !== undefined) {
return this.visit(node.input.value, kind, usecase)(input);
}

return [true];
}, 'input');
}

if (kind === 'result' && node.result) {
return addPath(this.visit(node.result.value, kind, usecase), 'result');
}

// input or result isn't defined
return (input: unknown): ValidationResult => {
if (
typeof input === 'undefined' ||
(typeof input === 'object' &&
(input === null || Object.keys(input).length === 0))
) {
if (isNone(input)) {
return [true];
}

if (isNonPrimitive(input) && Object.keys(input).length === 0) {
return [true];
}

Expand Down

0 comments on commit be46882

Please sign in to comment.