From 3e45e9561e109186ea038c6d3b67f54071ee8a67 Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Tue, 13 Sep 2022 16:40:51 +0000 Subject: [PATCH] Fix withDefault type helper to work with boolean `withDefault` is a type helper that allows to create a type that defaults to a default value when trying to decode a nullish value. That type was not correctly working with boolean types, causing `false` values to be replaced by true. This would specifically cause issues when parsing the target state, where a `running: false` in a service would become a `running: true` due to the type decoding. Change-type: patch --- src/types/state.ts | 5 +- .../lib/validation.spec.ts} | 69 ++++++++++++------- test/unit/types.spec.ts | 42 +++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) rename test/{legacy/06-validation.spec.ts => unit/lib/validation.spec.ts} (93%) create mode 100644 test/unit/types.spec.ts diff --git a/src/types/state.ts b/src/types/state.ts index b8432d4c..c977e745 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -123,17 +123,16 @@ export type DeviceState = { }; // Return a type with a default value -const withDefault = ( +export const withDefault = ( type: T, defaultValue: t.TypeOf, ): t.Type> => new t.Type( type.name, type.is, - (v, c) => type.validate(!!v ? v : defaultValue, c), + (v, c) => type.validate(v != null ? v : defaultValue, c), type.encode, ); - /** * Utility function to return a io-ts type from a native typescript * type. diff --git a/test/legacy/06-validation.spec.ts b/test/unit/lib/validation.spec.ts similarity index 93% rename from test/legacy/06-validation.spec.ts rename to test/unit/lib/validation.spec.ts index 12473028..1a261c14 100644 --- a/test/legacy/06-validation.spec.ts +++ b/test/unit/lib/validation.spec.ts @@ -286,33 +286,54 @@ describe('validation', () => { 'accepts apps with no services', ).to.be.true; - expect( - isRight( - TargetApps.decode({ - abcd: { - id: 1234, - name: 'something', - releases: { - bar: { - id: 123, - services: { - bazbaz: { - id: 45, - image_id: 34, - image: 'foo', - environment: { MY_SERVICE_ENV_VAR: '123' }, - labels: { 'io.balena.features.supervisor-api': 'true' }, - }, - }, - volumes: {}, - networks: {}, + const target = TargetApps.decode({ + abcd: { + id: 1234, + name: 'something', + releases: { + bar: { + id: 123, + services: { + bazbaz: { + id: 45, + image_id: 34, + image: 'foo', + environment: { MY_SERVICE_ENV_VAR: '123' }, + labels: { 'io.balena.features.supervisor-api': 'true' }, + running: false, }, }, + volumes: {}, + networks: {}, }, - }), - ), - 'accepts apps with a service', - ).to.be.true; + }, + }, + }); + expect(isRight(target), 'accepts apps with a service').to.be.true; + expect((target as any).right).to.deep.equal({ + abcd: { + id: 1234, + name: 'something', + class: 'fleet', + releases: { + bar: { + id: 123, + services: { + bazbaz: { + id: 45, + image_id: 34, + image: 'foo', + environment: { MY_SERVICE_ENV_VAR: '123' }, + labels: { 'io.balena.features.supervisor-api': 'true' }, + running: false, + }, + }, + volumes: {}, + networks: {}, + }, + }, + }, + }); }); it('rejects app with invalid environment', () => { diff --git a/test/unit/types.spec.ts b/test/unit/types.spec.ts new file mode 100644 index 00000000..744b6496 --- /dev/null +++ b/test/unit/types.spec.ts @@ -0,0 +1,42 @@ +import { expect } from 'chai'; +import * as t from 'io-ts'; + +import { withDefault } from '~/src/types'; + +describe('types', () => { + describe('withDefault', () => { + it('decodes object values', () => { + expect(withDefault(t.record(t.string, t.string), {}).decode(undefined)) + .to.have.property('right') + .that.deep.equals({}); + + expect(withDefault(t.record(t.string, t.string), {}).decode(null)) + .to.have.property('right') + .that.deep.equals({}); + + expect(withDefault(t.record(t.string, t.string), {}).decode({})) + .to.have.property('right') + .that.deep.equals({}); + + expect( + withDefault(t.record(t.string, t.string), {}).decode({ dummy: 'yes' }), + ) + .to.have.property('right') + .that.deep.equals({ dummy: 'yes' }); + }); + + it('decodes boolean values', () => { + expect(withDefault(t.boolean, true).decode(undefined)) + .to.have.property('right') + .that.equals(true); + + expect(withDefault(t.boolean, true).decode(null)) + .to.have.property('right') + .that.equals(true); + + expect(withDefault(t.boolean, true).decode(false)) + .to.have.property('right') + .that.equals(false); + }); + }); +});