diff --git a/api/package-lock.json b/api/package-lock.json index 0853482fb7..e7ef847cc5 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -354,6 +354,41 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", "dev": true }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz", + "integrity": "sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -572,6 +607,25 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.0.tgz", + "integrity": "sha512-jDZ55oCKxqlDmoTBBbBBEx+N8ZraUVhggMZ9T5t+6/Dh8/4NiOjSUfpLrPiEwxQDlAe3wpAkoXhWvE6LibtsMQ==", + "dev": true, + "requires": { + "@sinonjs/fake-timers": "^7.0.4" + } + }, + "@types/sinon-chai": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.5.tgz", + "integrity": "sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, "@types/swagger-schema-official": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/swagger-schema-official/-/swagger-schema-official-2.0.1.tgz", @@ -4865,6 +4919,12 @@ "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", "dev": true }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -5154,6 +5214,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -5828,6 +5894,45 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "nock": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", @@ -7438,6 +7543,52 @@ "is-arrayish": "^0.3.1" } }, + "sinon": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", + "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "sinon-chai": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.6.0.tgz", + "integrity": "sha512-bk2h+0xyKnmvazAnc7HE5esttqmCerSMcBtuB2PS2T4tG6x8woXAxZeJaOJWD+8reXHngnXn0RtIbfEW9OTHFg==", + "dev": true + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/api/package.json b/api/package.json index 847a28d006..911787d032 100644 --- a/api/package.json +++ b/api/package.json @@ -64,6 +64,8 @@ "@types/mocha": "~8.0.1", "@types/multer": "^1.4.5", "@types/pg": "~7.14.4", + "@types/sinon": "^10.0.0", + "@types/sinon-chai": "^3.2.5", "@types/uuid": "~8.3.0", "@types/yamljs": "~0.2.31", "@typescript-eslint/eslint-plugin": "~3.7.1", @@ -81,6 +83,8 @@ "npm-run-all": "~4.1.5", "nyc": "~15.1.0", "prettier": "~2.2.1", + "sinon": "^10.0.0", + "sinon-chai": "^3.6.0", "supertest": "~6.0.1", "ts-mocha": "~8.0.0", "ts-node": "~9.1.1" diff --git a/api/src/models/permit-no-sampling.test.ts b/api/src/models/permit-no-sampling.test.ts index 533be4253d..2e776cd517 100644 --- a/api/src/models/permit-no-sampling.test.ts +++ b/api/src/models/permit-no-sampling.test.ts @@ -33,10 +33,12 @@ describe('postPermitNoSamplingObject', () => { permit: { permits: [ { - permit_number: '123' + permit_number: '123', + permit_type: 'type 1' }, { - permit_number: '456' + permit_number: '456', + permit_type: 'type 2' } ] } @@ -60,10 +62,12 @@ describe('postPermitNoSamplingObject', () => { expect(postPermitNoSamplingObject.permit).to.deep.equal({ permits: [ { - permit_number: '123' + permit_number: '123', + permit_type: 'type 1' }, { - permit_number: '456' + permit_number: '456', + permit_type: 'type 2' } ] }); @@ -115,7 +119,7 @@ describe('PostPermitNoSamplingData', () => { describe('All values provided where permits is a valid array', () => { let postPermitNoSamplingData: PostPermitNoSamplingData; - const obj = { permits: [{ permit_number: 1 }] }; + const obj = { permits: [{ permit_number: 1, permit_type: 'type' }] }; before(() => { postPermitNoSamplingData = new PostPermitNoSamplingData(obj); @@ -124,7 +128,8 @@ describe('PostPermitNoSamplingData', () => { it('sets permits', function () { expect(postPermitNoSamplingData.permits).to.eql([ { - permit_number: 1 + permit_number: 1, + permit_type: 'type' } ]); }); diff --git a/api/src/models/permit-no-sampling.ts b/api/src/models/permit-no-sampling.ts index 30e4cd2464..c46e2a7290 100644 --- a/api/src/models/permit-no-sampling.ts +++ b/api/src/models/permit-no-sampling.ts @@ -23,6 +23,7 @@ export class PostPermitNoSamplingObject { export interface IPostPermitNoSampling { permit_number: string; + permit_type: string; } /** @@ -41,7 +42,8 @@ export class PostPermitNoSamplingData { (obj?.permits?.length && obj.permits.map((item: any) => { return { - permit_number: item.permit_number + permit_number: item.permit_number, + permit_type: item.permit_type }; })) || []; diff --git a/api/src/paths/permit-no-sampling.ts b/api/src/paths/permit-no-sampling.ts index 2c91ceb89f..b320ffe739 100644 --- a/api/src/paths/permit-no-sampling.ts +++ b/api/src/paths/permit-no-sampling.ts @@ -76,7 +76,7 @@ function createNoSamplePermits(): RequestHandler { const result = await Promise.all( sanitizedNoSamplePermitPostData.permit.permits.map((permit: IPostPermitNoSampling) => - insertNoSamplePermitNumber(permit, sanitizedNoSamplePermitPostData.coordinator, connection) + insertNoSamplePermit(permit, sanitizedNoSamplePermitPostData.coordinator, connection) ) ); @@ -93,7 +93,7 @@ function createNoSamplePermits(): RequestHandler { }; } -export const insertNoSamplePermitNumber = async ( +export const insertNoSamplePermit = async ( permit: IPostPermitNoSampling, coordinator: PostCoordinatorData, connection: IDBConnection diff --git a/api/src/queries/permit-no-sampling/permit-no-sampling-queries.test.ts b/api/src/queries/permit-no-sampling/permit-no-sampling-queries.test.ts index baa0d373ff..2425dda627 100644 --- a/api/src/queries/permit-no-sampling/permit-no-sampling-queries.test.ts +++ b/api/src/queries/permit-no-sampling/permit-no-sampling-queries.test.ts @@ -15,6 +15,7 @@ describe('postPermitNoSamplingSQL', () => { it('returns a SQLStatement when all fields are passed in as expected', () => { const response = postPermitNoSamplingSQL({ permit_number: '123', + permit_type: 'permit type', first_name: 'first', last_name: 'last', email_address: 'email', @@ -24,7 +25,7 @@ describe('postPermitNoSamplingSQL', () => { expect(response).to.not.be.null; - expect(response?.values.length).to.equal(5); + expect(response?.values.length).to.equal(6); expect(response?.values).to.deep.include('123'); expect(response?.values).to.deep.include('first'); diff --git a/api/src/queries/permit-no-sampling/permit-no-sampling-queries.ts b/api/src/queries/permit-no-sampling/permit-no-sampling-queries.ts index 1ea267d68e..ca9435e7e4 100644 --- a/api/src/queries/permit-no-sampling/permit-no-sampling-queries.ts +++ b/api/src/queries/permit-no-sampling/permit-no-sampling-queries.ts @@ -27,12 +27,14 @@ export const postPermitNoSamplingSQL = ( const sqlStatement: SQLStatement = SQL` INSERT INTO no_sample_permit ( number, + type, coordinator_first_name, coordinator_last_name, coordinator_email_address, coordinator_agency_name ) VALUES ( ${noSamplePermit.permit_number}, + ${noSamplePermit.permit_type}, ${noSamplePermit.first_name}, ${noSamplePermit.last_name}, ${noSamplePermit.email_address}, diff --git a/api/src/security/auth-utils.test.ts b/api/src/security/auth-utils.test.ts index e9ad228983..d4268727c5 100644 --- a/api/src/security/auth-utils.test.ts +++ b/api/src/security/auth-utils.test.ts @@ -1,30 +1,38 @@ -import { expect } from 'chai'; +import chai, { expect } from 'chai'; import { describe } from 'mocha'; -import { userHasValidSystemRoles } from './auth-utils'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import * as auth_utils from './auth-utils'; +import * as db from '../database/db'; +import * as user_queries from '../queries/users/user-queries'; +import { QueryResult } from 'pg'; +import SQL from 'sql-template-strings'; + +chai.use(sinonChai); describe('userHasValidSystemRoles', () => { describe('validSystemRoles is a string', () => { describe('userSystemRoles is a string', () => { it('returns true if the valid roles is empty', () => { - const response = userHasValidSystemRoles('', ''); + const response = auth_utils.userHasValidSystemRoles('', ''); expect(response).to.be.true; }); it('returns false if the user has no roles', () => { - const response = userHasValidSystemRoles('admin', ''); + const response = auth_utils.userHasValidSystemRoles('admin', ''); expect(response).to.be.false; }); it('returns false if the user has no matching roles', () => { - const response = userHasValidSystemRoles('admin', 'user'); + const response = auth_utils.userHasValidSystemRoles('admin', 'user'); expect(response).to.be.false; }); it('returns true if the user has a matching role', () => { - const response = userHasValidSystemRoles('admin', 'admin'); + const response = auth_utils.userHasValidSystemRoles('admin', 'admin'); expect(response).to.be.true; }); @@ -32,25 +40,25 @@ describe('userHasValidSystemRoles', () => { describe('userSystemRoles is an array', () => { it('returns true if the valid roles is empty', () => { - const response = userHasValidSystemRoles('', []); + const response = auth_utils.userHasValidSystemRoles('', []); expect(response).to.be.true; }); it('returns false if the user has no matching roles', () => { - const response = userHasValidSystemRoles('admin', []); + const response = auth_utils.userHasValidSystemRoles('admin', []); expect(response).to.be.false; }); it('returns false if the user has no matching roles', () => { - const response = userHasValidSystemRoles('admin', ['user']); + const response = auth_utils.userHasValidSystemRoles('admin', ['user']); expect(response).to.be.false; }); it('returns true if the user has a matching role', () => { - const response = userHasValidSystemRoles('admin', ['admin']); + const response = auth_utils.userHasValidSystemRoles('admin', ['admin']); expect(response).to.be.true; }); @@ -60,25 +68,25 @@ describe('userHasValidSystemRoles', () => { describe('validSystemRoles is an array', () => { describe('userSystemRoles is a string', () => { it('returns true if the valid roles is empty', () => { - const response = userHasValidSystemRoles([], ''); + const response = auth_utils.userHasValidSystemRoles([], ''); expect(response).to.be.true; }); it('returns false if the user has no roles', () => { - const response = userHasValidSystemRoles(['admin'], ''); + const response = auth_utils.userHasValidSystemRoles(['admin'], ''); expect(response).to.be.false; }); it('returns false if the user has no matching roles', () => { - const response = userHasValidSystemRoles(['admin'], 'user'); + const response = auth_utils.userHasValidSystemRoles(['admin'], 'user'); expect(response).to.be.false; }); it('returns true if the user has a matching role', () => { - const response = userHasValidSystemRoles(['admin'], 'admin'); + const response = auth_utils.userHasValidSystemRoles(['admin'], 'admin'); expect(response).to.be.true; }); @@ -86,28 +94,183 @@ describe('userHasValidSystemRoles', () => { describe('userSystemRoles is an array', () => { it('returns true if the valid roles is empty', () => { - const response = userHasValidSystemRoles([], []); + const response = auth_utils.userHasValidSystemRoles([], []); expect(response).to.be.true; }); it('returns false if the user has no matching roles', () => { - const response = userHasValidSystemRoles(['admin'], []); + const response = auth_utils.userHasValidSystemRoles(['admin'], []); expect(response).to.be.false; }); it('returns false if the user has no matching roles', () => { - const response = userHasValidSystemRoles(['admin'], ['user']); + const response = auth_utils.userHasValidSystemRoles(['admin'], ['user']); expect(response).to.be.false; }); it('returns true if the user has a matching role', () => { - const response = userHasValidSystemRoles(['admin'], ['admin']); + const response = auth_utils.userHasValidSystemRoles(['admin'], ['admin']); expect(response).to.be.true; }); }); }); }); + +describe('getSystemUser', function () { + afterEach(() => { + sinon.restore(); + }); + + const keycloakToken = { + key: 'value' + }; + + const dbConnectionObj = { + systemUserId: () => { + return null; + }, + open: async () => { + // do nothing + }, + release: () => { + // do nothing + }, + commit: async () => { + // do nothing + }, + rollback: async () => { + // do nothing + }, + query: async () => { + // do nothing + } + }; + + it('should return null when no system user id', async function () { + sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); + + const result = await auth_utils.getSystemUser(keycloakToken); + + expect(result).to.be.null; + }); + + it('should return null when getUserByIdSql fails', async function () { + sinon.stub(db, 'getDBConnection').returns({ + ...dbConnectionObj, + systemUserId: () => { + return 20; + } + }); + + sinon.stub(user_queries, 'getUserByIdSQL').returns(null); + + const result = await auth_utils.getSystemUser(keycloakToken); + + expect(result).to.be.null; + }); + + it('should return the user row on success', async function () { + sinon.stub(db, 'getDBConnection').returns({ + ...dbConnectionObj, + systemUserId: () => { + return 20; + }, + query: async () => { + return { + rowCount: 1, + rows: [ + { + id: 1, + user_identifier: 'identifier', + role_ids: [1, 2], + role_names: ['role 1', 'role 2'] + } + ] + } as QueryResult; + } + }); + + sinon.stub(user_queries, 'getUserByIdSQL').returns(SQL`some query`); + + const result = await auth_utils.getSystemUser(keycloakToken); + + expect(result.id).to.equal(1); + expect(result.user_identifier).to.equal('identifier'); + expect(result.role_ids).to.eql([1, 2]); + expect(result.role_names).to.eql(['role 1', 'role 2']); + }); + + it('should return null when response has no rowCount (no user found)', async function () { + sinon.stub(db, 'getDBConnection').returns({ + ...dbConnectionObj, + systemUserId: () => { + return 20; + }, + query: async () => { + return ({ + rowCount: 0, + rows: [] + } as unknown) as QueryResult; + } + }); + + sinon.stub(user_queries, 'getUserByIdSQL').returns(SQL`some query`); + + const result = await auth_utils.getSystemUser(keycloakToken); + + expect(result).to.be.null; + }); + + it('should throw an error when a failure occurs', async function () { + const expectedError = new Error('cannot process query'); + + sinon.stub(db, 'getDBConnection').returns({ + ...dbConnectionObj, + systemUserId: () => { + throw expectedError; + } + }); + + try { + await auth_utils.getSystemUser(keycloakToken); + expect.fail(); + } catch (actualError) { + expect(actualError.message).to.equal(expectedError.message); + } + }); +}); + +describe('authorize', function () { + afterEach(() => { + sinon.restore(); + }); + + it('throws HTTP403 when the keycloak_token is empty', async function () { + try { + await auth_utils.authorize({ keycloak_token: '' }, ['abc']); + expect.fail(); + } catch (actualError) { + expect(actualError.message).to.contain('Access Denied'); + } + }); + + it('returns true without scopes', async function () { + const result = await auth_utils.authorize({ keycloak_token: 'some token' }, []); + expect(result).to.be.true; + }); + + it('throws HTTP403 when stubbed getSystemUser returns null', async function () { + sinon.stub(auth_utils, 'getSystemUser').resolves(null); + + try { + await auth_utils.authorize({ keycloak_token: 'some token' }, ['abc']); + expect.fail(); + } catch (actualError) { + expect(actualError.message).to.contain('Access Denied'); + } + }); +}); diff --git a/api/src/security/auth-utils.ts b/api/src/security/auth-utils.ts index c80c262619..931f4029a4 100644 --- a/api/src/security/auth-utils.ts +++ b/api/src/security/auth-utils.ts @@ -165,6 +165,7 @@ export const authorize = async function (req: any, scopes: string[]): Promise { */ const handleSubmit = async () => { const invalidStepIndex = getFirstInvalidFormStep(); + const isFullProject = isSamplingConducted(stepForms[1].stepValues); + const draftId = Number(queryParams.draftId); - if (invalidStepIndex >= 0) { + if ((isFullProject && invalidStepIndex >= 0) || (!isFullProject && [0, 1].includes(invalidStepIndex))) { setActiveStep(invalidStepIndex); showCreateErrorDialog({ @@ -481,9 +483,6 @@ const CreateProjectPage: React.FC = () => { return; } - const isFullProject = isSamplingConducted(stepForms[1].stepValues); - const draftId = Number(queryParams.draftId); - try { if (!isFullProject) { const response = await createPermitNoSampling({