diff --git a/sequencing-server/cdict/channel_banananation.xml b/sequencing-server/cdict/channel_banananation.xml new file mode 100644 index 0000000000..0a3e214a1d --- /dev/null +++ b/sequencing-server/cdict/channel_banananation.xml @@ -0,0 +1,35 @@ + + +
+ + + +
+ + + + + + + + + + + OVEN-21 + OVEN-26 + + + + + 1 + String of the oven + + None + + oven_sc + BAKE + + + + +
diff --git a/sequencing-server/cdict/parameter_banananation.xml b/sequencing-server/cdict/parameter_banananation.xml new file mode 100644 index 0000000000..03f9033a86 --- /dev/null +++ b/sequencing-server/cdict/parameter_banananation.xml @@ -0,0 +1,34 @@ + + +
+ + + +
+ + + + + + + + + + The rate that the banana changes form green to yellow + + + banana_mgr + BANANA + + + + + + + + + + 1 + This is the rate that the banana can change color for all bananas + +
diff --git a/sequencing-server/test/batchLoaders/commandDictionaryTypescriptBatchLoader.spec.ts b/sequencing-server/test/batchLoaders/DictionaryTypescriptBatchLoader.spec.ts similarity index 100% rename from sequencing-server/test/batchLoaders/commandDictionaryTypescriptBatchLoader.spec.ts rename to sequencing-server/test/batchLoaders/DictionaryTypescriptBatchLoader.spec.ts diff --git a/sequencing-server/test/batchLoaders/expansionBatchLoader.spec.ts b/sequencing-server/test/batchLoaders/expansionBatchLoader.spec.ts index a4d709658c..b9b53e8562 100644 --- a/sequencing-server/test/batchLoaders/expansionBatchLoader.spec.ts +++ b/sequencing-server/test/batchLoaders/expansionBatchLoader.spec.ts @@ -2,18 +2,31 @@ import type { GraphQLClient } from 'graphql-request'; import { insertExpansion, removeExpansion } from '../testUtils/Expansion'; import { expansionBatchLoader } from '../../src/lib/batchLoaders/expansionBatchLoader'; import { getGraphQLClient } from '../testUtils/testUtils'; -import { insertCommandDictionary, removeCommandDictionary } from '../testUtils/CommandDictionary'; +import { insertDictionary, removeDictionary } from '../testUtils/Dictionary'; import { insertParcel, removeParcel } from '../testUtils/Parcel'; +import { DictionaryType } from '../../src/types/types'; let graphqlClient: GraphQLClient; let expansionId: number; let commandDictionaryId: number; +let channelDictionaryId: number; +let parameterDictionaryId: number; let parcelId: number; beforeAll(async () => { graphqlClient = await getGraphQLClient(); - commandDictionaryId = (await insertCommandDictionary(graphqlClient)).id; - parcelId = (await insertParcel(graphqlClient, commandDictionaryId, 'expansionBatchLoaderTestParcel')).parcelId; + commandDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.COMMAND)).id; + channelDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.CHANNEL)).id; + parameterDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.PARAMETER)).id; + parcelId = ( + await insertParcel( + graphqlClient, + commandDictionaryId, + channelDictionaryId, + parameterDictionaryId, + 'expansionBatchLoaderTestParcel', + ) + ).parcelId; expansionId = await insertExpansion( graphqlClient, @@ -32,7 +45,9 @@ beforeAll(async () => { afterAll(async () => { await removeExpansion(graphqlClient, expansionId); await removeParcel(graphqlClient, parcelId); - await removeCommandDictionary(graphqlClient, commandDictionaryId); + await removeDictionary(graphqlClient, commandDictionaryId, DictionaryType.COMMAND); + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); }); it('should load expansion data', async () => { diff --git a/sequencing-server/test/batchLoaders/expansionSetBatchLoader.spec.ts b/sequencing-server/test/batchLoaders/expansionSetBatchLoader.spec.ts index 4ef42bc645..fbb6673109 100644 --- a/sequencing-server/test/batchLoaders/expansionSetBatchLoader.spec.ts +++ b/sequencing-server/test/batchLoaders/expansionSetBatchLoader.spec.ts @@ -2,21 +2,34 @@ import type { GraphQLClient } from 'graphql-request'; import { expansionSetBatchLoader } from '../../src/lib/batchLoaders/expansionSetBatchLoader.js'; import { removeMissionModel, uploadMissionModel } from '../testUtils/MissionModel.js'; import { insertExpansion, insertExpansionSet, removeExpansion, removeExpansionSet } from '../testUtils/Expansion'; -import { insertCommandDictionary, removeCommandDictionary } from '../testUtils/CommandDictionary'; +import { insertDictionary, removeDictionary } from '../testUtils/Dictionary'; import { getGraphQLClient } from '../testUtils/testUtils.js'; import { insertParcel, removeParcel } from '../testUtils/Parcel'; +import { DictionaryType } from '../../src/types/types'; let graphqlClient: GraphQLClient; let missionModelId: number; let expansionId: number; let expansionSetId: number; let commandDictionaryId: number; +let channelDictionaryId: number; +let paramaterDictionaryId: number; let parcelId: number; beforeAll(async () => { graphqlClient = await getGraphQLClient(); - commandDictionaryId = (await insertCommandDictionary(graphqlClient)).id; - parcelId = (await insertParcel(graphqlClient, commandDictionaryId, 'expansionSetBatchLoaderTestParcel')).parcelId; + commandDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.COMMAND)).id; + channelDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.CHANNEL)).id; + paramaterDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.PARAMETER)).id; + parcelId = ( + await insertParcel( + graphqlClient, + commandDictionaryId, + channelDictionaryId, + paramaterDictionaryId, + 'expansionSetBatchLoaderTestParcel', + ) + ).parcelId; }); beforeAll(async () => { @@ -38,7 +51,9 @@ afterAll(async () => { await removeExpansionSet(graphqlClient, expansionSetId); await removeExpansion(graphqlClient, expansionId); await removeParcel(graphqlClient, parcelId); - await removeCommandDictionary(graphqlClient, commandDictionaryId); + await removeDictionary(graphqlClient, commandDictionaryId, DictionaryType.COMMAND); + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, paramaterDictionaryId, DictionaryType.PARAMETER); await removeMissionModel(graphqlClient, missionModelId); await removeMissionModel(graphqlClient, missionModelId); }); diff --git a/sequencing-server/test/command-dictionary.spec.ts b/sequencing-server/test/command-dictionary.spec.ts deleted file mode 100644 index ced5df6320..0000000000 --- a/sequencing-server/test/command-dictionary.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as ampcs from '@nasa-jpl/aerie-ampcs'; -import type { GraphQLClient } from 'graphql-request'; -import { - commandDictionaryString, - insertCommandDictionary, - removeCommandDictionary, -} from './testUtils/CommandDictionary.js'; -import { getGraphQLClient } from './testUtils/testUtils.js'; - -let graphqlClient: GraphQLClient; - -beforeAll(async () => { - graphqlClient = await getGraphQLClient(); -}); - -describe('upload command dictionary', () => { - it('should upload a command dictionary and all of the fields should be populated correctly', async () => { - // During the test we use a uuid for the mission so there's no conflicting command dictionaries. - const { id, dictionary_path, mission, parsed_json } = await insertCommandDictionary(graphqlClient); - - expect(dictionary_path).toBe(`/usr/src/app/sequencing_file_store/${mission}/command_lib.${mission}.ts`); - - expect(parsed_json).toStrictEqual( - ampcs.parse(commandDictionaryString.replace(/(Banana Nation|1.0.0.0)/g, mission)), - ); - - await removeCommandDictionary(graphqlClient, id); - }, 30000); -}); diff --git a/sequencing-server/test/command-expansion.spec.ts b/sequencing-server/test/command-expansion.spec.ts index 80cc39f79b..7d6f810c80 100644 --- a/sequencing-server/test/command-expansion.spec.ts +++ b/sequencing-server/test/command-expansion.spec.ts @@ -5,7 +5,7 @@ import { insertActivityDirective, removeActivityDirective, } from './testUtils/ActivityDirective.js'; -import { insertCommandDictionary, removeCommandDictionary } from './testUtils/CommandDictionary.js'; +import { insertDictionary, removeDictionary } from './testUtils/Dictionary'; import { expand, getExpandedSequence, @@ -22,17 +22,30 @@ import { executeSimulation, removeSimulationArtifacts, updateSimulationBounds } import { getGraphQLClient, waitMs } from './testUtils/testUtils'; import { insertSequence, linkActivityInstance } from './testUtils/Sequence.js'; import { insertParcel, removeParcel } from './testUtils/Parcel'; +import { DictionaryType } from '../src/types/types'; let planId: number; let graphqlClient: GraphQLClient; let missionModelId: number; let commandDictionaryId: number; +let channelDictionaryId: number; +let parameterDictionaryId: number; let parcelId: number; beforeAll(async () => { graphqlClient = await getGraphQLClient(); - commandDictionaryId = (await insertCommandDictionary(graphqlClient)).id; - parcelId = (await insertParcel(graphqlClient, commandDictionaryId, 'expansionTestParcel')).parcelId; + commandDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.COMMAND)).id; + channelDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.CHANNEL)).id; + parameterDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.PARAMETER)).id; + parcelId = ( + await insertParcel( + graphqlClient, + commandDictionaryId, + channelDictionaryId, + parameterDictionaryId, + 'expansionTestParcel', + ) + ).parcelId; }); beforeEach(async () => { @@ -47,7 +60,9 @@ beforeEach(async () => { afterAll(async () => { await removeParcel(graphqlClient, parcelId); - await removeCommandDictionary(graphqlClient, commandDictionaryId); + await removeDictionary(graphqlClient, commandDictionaryId, DictionaryType.COMMAND); + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); }); afterEach(async () => { @@ -493,8 +508,66 @@ describe('expansion', () => { expect(testProvidedExpansionSetId).toBeNumber(); const testProvidedResp = await getExpansionSet(graphqlClient, testProvidedExpansionSetId); - expect(testProvidedResp.expansion_set_by_pk.name).toBe(name); - expect(testProvidedResp.expansion_set_by_pk.description).toBe(description); + expect(testProvidedResp).not.toBeNull(); + expect(testProvidedResp?.name).toBe(name); + expect(testProvidedResp?.description).toBe(description); + + const testDefaultExpansionSetId = await insertExpansionSet(graphqlClient, parcelId, missionModelId, [expansionId]); + expect(testDefaultExpansionSetId).not.toBeNull(); + expect(testDefaultExpansionSetId).toBeDefined(); + expect(testDefaultExpansionSetId).toBeNumber(); + + const testDefaultResp = await getExpansionSet(graphqlClient, testDefaultExpansionSetId); + expect(testDefaultResp).not.toBeNull(); + expect(testDefaultResp?.name).toBe(''); + expect(testDefaultResp?.description).toBe(''); + + // Cleanup + await removeExpansion(graphqlClient, expansionId); + await removeExpansionSet(graphqlClient, testProvidedExpansionSetId); + await removeExpansionSet(graphqlClient, testProvidedExpansionSetId); + }); + + it('should handle optional channel and parameter dictionaries', async () => { + // Remove the optional dictionarys + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); + + const expansionId = await insertExpansion( + graphqlClient, + 'GrowBanana', + ` + export default function SingleCommandExpansion(props: { + activityInstance: ActivityType, + channelDictionary: ChannelDictionary | null, + parameterDictionaries : ParameterDictionary[] + }): ExpansionReturn { + return [ + A\`2023-091T10:00:00.000\`.ADD_WATER, + R\`04:00:00.000\`.GROW_BANANA({ quantity: 10, durationSecs: 7200 }) + ]; + } + `, + parcelId, + ); + const name = 'test name'; + const description = 'test desc'; + + const testProvidedExpansionSetId = await insertExpansionSet( + graphqlClient, + parcelId, + missionModelId, + [expansionId], + description, + name, + ); + expect(testProvidedExpansionSetId).not.toBeNull(); + expect(testProvidedExpansionSetId).toBeDefined(); + expect(testProvidedExpansionSetId).toBeNumber(); + + const testProvidedResp = await getExpansionSet(graphqlClient, testProvidedExpansionSetId); + expect(testProvidedResp?.name).toBe(name); + expect(testProvidedResp?.description).toBe(description); const testDefaultExpansionSetId = await insertExpansionSet(graphqlClient, parcelId, missionModelId, [expansionId]); expect(testDefaultExpansionSetId).not.toBeNull(); @@ -502,8 +575,8 @@ describe('expansion', () => { expect(testDefaultExpansionSetId).toBeNumber(); const testDefaultResp = await getExpansionSet(graphqlClient, testDefaultExpansionSetId); - expect(testDefaultResp.expansion_set_by_pk.name).toBe(''); - expect(testDefaultResp.expansion_set_by_pk.description).toBe(''); + expect(testDefaultResp?.name).toBe(''); + expect(testDefaultResp?.description).toBe(''); // Cleanup await removeExpansion(graphqlClient, expansionId); diff --git a/sequencing-server/test/command-types.spec.ts b/sequencing-server/test/command-types.spec.ts index ca4b91552f..ef9e4d1805 100644 --- a/sequencing-server/test/command-types.spec.ts +++ b/sequencing-server/test/command-types.spec.ts @@ -1,18 +1,25 @@ import { gql, GraphQLClient } from 'graphql-request'; import { Status } from '../src/common.js'; -import { insertCommandDictionary, removeCommandDictionary } from './testUtils/CommandDictionary.js'; +import { insertDictionary, removeDictionary } from './testUtils/Dictionary'; import { getGraphQLClient } from './testUtils/testUtils.js'; +import { DictionaryType } from '../src/types/types'; let graphqlClient: GraphQLClient; let commandDictionaryId: number; +let channelDictionaryId: number; +let parameterDictionaryId: number; beforeAll(async () => { graphqlClient = await getGraphQLClient(); - commandDictionaryId = (await insertCommandDictionary(graphqlClient)).id; + commandDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.COMMAND)).id; + channelDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.CHANNEL)).id; + parameterDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.PARAMETER)).id; }); afterAll(async () => { - removeCommandDictionary(graphqlClient, commandDictionaryId); + await removeDictionary(graphqlClient, commandDictionaryId, DictionaryType.COMMAND); + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); }); it('should return command types', async () => { diff --git a/sequencing-server/test/db-state.spec.ts b/sequencing-server/test/db-state.spec.ts new file mode 100644 index 0000000000..f96f024821 --- /dev/null +++ b/sequencing-server/test/db-state.spec.ts @@ -0,0 +1,164 @@ +import type { GraphQLClient } from 'graphql-request'; +import { getDictionary, insertDictionary, removeDictionary } from './testUtils/Dictionary'; +import { getGraphQLClient } from './testUtils/testUtils.js'; +import { DictionaryType } from '../src/types/types'; +import { removeMissionModel, uploadMissionModel } from './testUtils/MissionModel'; +import { getParcel, insertParcel, removeParcel } from './testUtils/Parcel'; +import { + getExpansion, + getExpansionSet, + insertExpansion, + insertExpansionSet, + removeExpansion, + removeExpansionSet, +} from './testUtils/Expansion'; +let graphqlClient: GraphQLClient; +let missionModelId: number; +let commandDictonaryId: number; +let channelDictionaryId: number; +let parameterDictionaryId: number; +let parcelId: number; +const expansion_rule: string = `export default function MyExpansion(props: { + activityInstance: ActivityType, + channelDictionary: ChannelDictionary | null + parameterDictionaries : ParameterDictionary[] + }): ExpansionReturn { + const { activityInstance, channelDictionary, parameterDictionaries } = props; + return []; + }`; + +beforeAll(async () => { + graphqlClient = await getGraphQLClient(); + missionModelId = await uploadMissionModel(graphqlClient); +}); + +beforeEach(async () => { + commandDictonaryId = (await insertDictionary(graphqlClient, DictionaryType.COMMAND)).id; + channelDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.CHANNEL)).id; + parameterDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.PARAMETER)).id; + parcelId = ( + await insertParcel(graphqlClient, commandDictonaryId, channelDictionaryId, parameterDictionaryId, 'db-parcel-test') + ).parcelId; +}, 10000); + +afterEach(async () => { + await removeDictionary(graphqlClient, commandDictonaryId, DictionaryType.COMMAND); + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); + await removeParcel(graphqlClient, parcelId); +}); + +afterAll(async () => { + await removeMissionModel(graphqlClient, missionModelId); +}); + +describe('Sequencing DB State', () => { + it('Delete Command Dictionary should remove parcel, and expansion set, but keep expansion rule', async () => { + const expansionID = await insertExpansion(graphqlClient, 'BakeBananaBread', expansion_rule, parcelId); + + const setID = await insertExpansionSet( + graphqlClient, + parcelId, + missionModelId, + [expansionID], + 'db state test', + 'db-state set', + ); + + // Command Dictionary is deleted + await removeDictionary(graphqlClient, commandDictonaryId, DictionaryType.COMMAND); + + // Parcel should not exist + const parcel = await getParcel(graphqlClient, parcelId); + expect(parcel).toBeNull(); + + // Expansion Set should not exist + const expansionSet = await getExpansionSet(graphqlClient, setID); + expect(expansionSet).toBeNull(); + + // expansion rule should exist, with no reference to the parcel + const expansion = await getExpansion(graphqlClient, expansionID); + expect(expansion.parcel_id).toBeNull(); + expect(expansion.id).toEqual(expansionID); + + // cleanup + await removeExpansion(graphqlClient, expansionID); + }, 30000); + + it('Delete channel or parameter Dictionary should NOT remove parcel, expansion set, and expansion rule', async () => { + const expansionID = await insertExpansion(graphqlClient, 'BakeBananaBread', expansion_rule, parcelId); + + const setID = await insertExpansionSet( + graphqlClient, + parcelId, + missionModelId, + [expansionID], + 'db state test', + 'db-state set', + ); + + // Remove the channel and parameter dictionary + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); + + // Parcel should exist + const parcel = await getParcel(graphqlClient, parcelId); + expect(parcel?.id).toEqual(parcelId); + + // Expansion Set should exist + const expansionSet = await getExpansionSet(graphqlClient, setID); + expect(expansionSet?.id).toEqual(setID); + expect(expansionSet?.parcel_id).toEqual(parcelId); + expect(expansionSet?.mission_model_id).toEqual(missionModelId); + expect(expansionSet?.expansion_rules[0]?.id).toEqual(expansionID); + + // expansion rule should exist, with a reference to the parcel + const expansion = await getExpansion(graphqlClient, expansionID); + expect(expansion.parcel_id).toEqual(parcelId); + expect(expansion.id).toEqual(expansionID); + + // cleanup + await removeExpansionSet(graphqlClient, setID); + await removeExpansion(graphqlClient, expansionID); + }, 30000); + + it('Delete Parcel should NOT remove dictionaries, or expansion rule, but remove expansion sets', async () => { + const expansionID = await insertExpansion(graphqlClient, 'BakeBananaBread', expansion_rule, parcelId); + + const setID = await insertExpansionSet( + graphqlClient, + parcelId, + missionModelId, + [expansionID], + 'db state test', + 'db-state set', + ); + + // Remove the channel and parameter dictionary + await removeParcel(graphqlClient, parcelId); + + // Parcel should exist + const parcel = await getParcel(graphqlClient, parcelId); + expect(parcel).toBeNull(); + + // Command, Channel, and Parameter Dictionary should exist + const commandDictionary = await getDictionary(graphqlClient, commandDictonaryId, DictionaryType.COMMAND); + expect(commandDictionary?.id).toEqual(commandDictonaryId); + const channelDictionary = await getDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + expect(channelDictionary?.id).toEqual(channelDictionaryId); + const parameterDictionary = await getDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); + expect(parameterDictionary?.id).toEqual(parameterDictionaryId); + + // Expansion Set should not exist + const expansionSet = await getExpansionSet(graphqlClient, setID); + expect(expansionSet).toBeNull(); + + // expansion rule should exist, with no reference to the parcel + const expansion = await getExpansion(graphqlClient, expansionID); + expect(expansion.parcel_id).toBeNull(); + expect(expansion.id).toEqual(expansionID); + + // cleanup + await removeExpansion(graphqlClient, expansionID); + }, 30000); +}); diff --git a/sequencing-server/test/dictionary.spec.ts b/sequencing-server/test/dictionary.spec.ts new file mode 100644 index 0000000000..02c93dcd7a --- /dev/null +++ b/sequencing-server/test/dictionary.spec.ts @@ -0,0 +1,61 @@ +import * as ampcs from '@nasa-jpl/aerie-ampcs'; +import type { GraphQLClient } from 'graphql-request'; +import { + channelDictionaryString, + commandDictionaryString, + insertDictionary, + parameterDictionaryString, + removeDictionary, +} from './testUtils/Dictionary'; +import { getGraphQLClient } from './testUtils/testUtils.js'; +import { DictionaryType } from '../src/types/types'; + +let graphqlClient: GraphQLClient; + +beforeAll(async () => { + graphqlClient = await getGraphQLClient(); +}); + +describe('upload dictionaries', () => { + it('should upload a command dictionary and all of the fields should be populated correctly', async () => { + // During the test we use a uuid for the mission so there's no conflicting command dictionaries. + const { id, dictionary_path, mission, parsed_json } = await insertDictionary(graphqlClient, DictionaryType.COMMAND); + + expect(dictionary_path).toBe(`/usr/src/app/sequencing_file_store/${mission}/command_lib.${mission}.ts`); + + expect(parsed_json).toStrictEqual( + ampcs.parse(commandDictionaryString.replace(/(Banana Nation|1.0.0.0)/g, mission)), + ); + + await removeDictionary(graphqlClient, id, DictionaryType.COMMAND); + }, 30000); + + it('should upload a channel dictionary and all of the fields should be populated correctly', async () => { + // During the test we use a uuid for the mission so there's no conflicting command dictionaries. + const { id, dictionary_path, mission, parsed_json } = await insertDictionary(graphqlClient, DictionaryType.CHANNEL); + + expect(dictionary_path).toBe(`/usr/src/app/sequencing_file_store/${mission}/channel_lib.${mission}.ts`); + + expect(parsed_json).toEqual( + ampcs.parseChannelDictionary(channelDictionaryString.replace(/(Banana Nation|1.0.0.0)/g, mission)), + ); + + await removeDictionary(graphqlClient, id, DictionaryType.CHANNEL); + }, 30000); + + it('should upload a parameter dictionary and all of the fields should be populated correctly', async () => { + // During the test we use a uuid for the mission so there's no conflicting command dictionaries. + const { id, dictionary_path, mission, parsed_json } = await insertDictionary( + graphqlClient, + DictionaryType.PARAMETER, + ); + + expect(dictionary_path).toBe(`/usr/src/app/sequencing_file_store/${mission}/parameter_lib.${mission}.ts`); + + expect(parsed_json).toEqual( + ampcs.parseParameterDictionary(parameterDictionaryString.replace(/(Banana Nation|1.0.0.1)/g, mission)), + ); + + await removeDictionary(graphqlClient, id, DictionaryType.PARAMETER); + }, 30000); +}); diff --git a/sequencing-server/test/sequence-generation.spec.ts b/sequencing-server/test/sequence-generation.spec.ts index bc572d3551..683819c300 100644 --- a/sequencing-server/test/sequence-generation.spec.ts +++ b/sequencing-server/test/sequence-generation.spec.ts @@ -1,12 +1,12 @@ import type { GraphQLClient } from 'graphql-request'; import { TimingTypes } from '../src/lib/codegen/CommandEDSLPreface.js'; -import { FallibleStatus } from '../src/types/types'; +import { DictionaryType, FallibleStatus } from '../src/types/types'; import { convertActivityDirectiveIdToSimulatedActivityId, insertActivityDirective, removeActivityDirective, } from './testUtils/ActivityDirective.js'; -import { insertCommandDictionary, removeCommandDictionary } from './testUtils/CommandDictionary.js'; +import { insertDictionary, removeDictionary } from './testUtils/Dictionary'; import { expand, insertExpansion, @@ -32,12 +32,24 @@ let planId: number; let graphqlClient: GraphQLClient; let missionModelId: number; let commandDictionaryId: number; +let channelDictionaryId: number; +let parameterDictionaryId: number; let parcelId: number; beforeAll(async () => { graphqlClient = await getGraphQLClient(); - commandDictionaryId = (await insertCommandDictionary(graphqlClient)).id; - parcelId = (await insertParcel(graphqlClient, commandDictionaryId, 'sequenceGenerationTestParcel')).parcelId; + commandDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.COMMAND)).id; + channelDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.CHANNEL)).id; + parameterDictionaryId = (await insertDictionary(graphqlClient, DictionaryType.PARAMETER)).id; + parcelId = ( + await insertParcel( + graphqlClient, + commandDictionaryId, + channelDictionaryId, + parameterDictionaryId, + 'sequenceGenerationTestParcel', + ) + ).parcelId; }); beforeEach(async () => { @@ -52,7 +64,9 @@ beforeEach(async () => { afterAll(async () => { await removeParcel(graphqlClient, parcelId); - await removeCommandDictionary(graphqlClient, commandDictionaryId); + await removeDictionary(graphqlClient, commandDictionaryId, DictionaryType.COMMAND); + await removeDictionary(graphqlClient, channelDictionaryId, DictionaryType.CHANNEL); + await removeDictionary(graphqlClient, parameterDictionaryId, DictionaryType.PARAMETER); }); afterEach(async () => { @@ -65,6 +79,7 @@ describe('sequence generation', () => { let expansionId2: number; let expansionId3: number; let expansionId4: number; + let expansionId5: number; beforeEach(async () => { expansionId1 = await insertExpansion( @@ -157,6 +172,25 @@ describe('sequence generation', () => { `, parcelId, ); + + expansionId5 = await insertExpansion( + graphqlClient, + 'BananaNap', + ` + export default function MyExpansion(props: { + activityInstance: ActivityType, + channelDictionary: ChannelDictionary | null + parameterDictionaries : ParameterDictionary[] + }): ExpansionReturn { + const { activityInstance, channelDictionary, parameterDictionaries } = props; + return [ + ...(channelDictionary ? channelDictionary.telemetries.map(t => C.ECHO('Telemetry Name: '+t.name)) : []), + ...parameterDictionaries[0].params.map(p => C.ECHO('Parameter Name: '+p.param_name)) + ]; + } + `, + parcelId, + ); }); afterEach(async () => { @@ -1145,7 +1179,7 @@ describe('sequence generation', () => { it('should work for throwing expansions', async () => { /** Begin Setup */ // Add throwing expansion - const expansionId5 = await insertExpansion( + const localExpansionId = await insertExpansion( graphqlClient, 'BiteBanana', ` @@ -1161,7 +1195,7 @@ describe('sequence generation', () => { expansionId1, expansionId2, expansionId3, - expansionId5, + localExpansionId, ]); // Create Activity Directives @@ -1482,14 +1516,14 @@ describe('sequence generation', () => { removeActivityDirective(graphqlClient, activityId2, planId), removeActivityDirective(graphqlClient, activityId3, planId), ]); - await removeExpansion(graphqlClient, expansionId5); + await removeExpansion(graphqlClient, localExpansionId); await removeExpansionSet(graphqlClient, expansionSetId); /** End Cleanup */ }, 30000); it('should work for throwing expansions in bulk', async () => { /** Begin Setup */ - const expansionId5 = await insertExpansion( + const localExpansionId = await insertExpansion( graphqlClient, 'BiteBanana', ` @@ -1504,7 +1538,7 @@ describe('sequence generation', () => { expansionId1, expansionId2, expansionId3, - expansionId5, + localExpansionId, ]); // Create Activity Directives @@ -2126,7 +2160,7 @@ describe('sequence generation', () => { removeActivityDirective(graphqlClient, activityId7, planId), removeActivityDirective(graphqlClient, activityId8, planId), ]); - await removeExpansion(graphqlClient, expansionId5); + await removeExpansion(graphqlClient, localExpansionId); await removeExpansionSet(graphqlClient, expansionSetId); /** End Cleanup */ }, 30000); @@ -3677,6 +3711,124 @@ describe('sequence generation', () => { /** End Cleanup */ }, 30000); }); + + it('Channel and Parameter Dictionary', async () => { + /** Begin Setup */ + // Create Expansion Set + const expansionSetId = await insertExpansionSet(graphqlClient, parcelId, missionModelId, [ + expansionId1, + expansionId2, + expansionId5, + ]); + + // Create Activity Directives + const [activityId1, activityId2] = await Promise.all([ + insertActivityDirective(graphqlClient, planId, 'BiteBanana', '90 minutes'), // non-existent expansion + + insertActivityDirective(graphqlClient, planId, 'BananaNap', '230 minutes'), + ]); + + // Simulate Plan + const simulationArtifactPk = await executeSimulation(graphqlClient, planId); + // Expand Plan to Sequence Fragments + const expansionRunPk = await expand(graphqlClient, expansionSetId, simulationArtifactPk.simulationDatasetId); + // Create Sequence + const [sequencePk1, sequencePk2] = await Promise.all([ + insertSequence(graphqlClient, { + seqId: 'test00000', + simulationDatasetId: simulationArtifactPk.simulationDatasetId, + }), + insertSequence(graphqlClient, { + seqId: 'test00001', + simulationDatasetId: simulationArtifactPk.simulationDatasetId, + }), + ]); + // Link Activity Instances to Sequence + await Promise.all([ + linkActivityInstance(graphqlClient, sequencePk1, activityId1), + linkActivityInstance(graphqlClient, sequencePk2, activityId2), + ]); + + // Get the simulated activity ids + const [ + _simulatedActivityId1, // No expansion, so no check required on this one + simulatedActivityId2, + ] = await Promise.all([ + convertActivityDirectiveIdToSimulatedActivityId( + graphqlClient, + simulationArtifactPk.simulationDatasetId, + activityId1, + ), + convertActivityDirectiveIdToSimulatedActivityId( + graphqlClient, + simulationArtifactPk.simulationDatasetId, + activityId2, + ), + ]); + + /** End Setup */ + + // Retrieve seqJson + const getSequenceSeqJsonResponse = await getSequenceSeqJsonBulk(graphqlClient, [ + { seqId: 'test00000', simulationDatasetId: simulationArtifactPk.simulationDatasetId }, + { seqId: 'test00001', simulationDatasetId: simulationArtifactPk.simulationDatasetId }, + ]); + + const secondSequence = getSequenceSeqJsonResponse[1]!; + + if (secondSequence.status !== FallibleStatus.SUCCESS) { + throw secondSequence.errors; + } + + expect(secondSequence.seqJson.id).toBe('test00001'); + expect(secondSequence.seqJson.metadata).toEqual({ + planId: planId, + simulationDatasetId: simulationArtifactPk.simulationDatasetId, + timeSorted: false, + }); + expect(secondSequence.seqJson.steps).toEqual([ + { + args: [ + { + name: 'echo_string', + type: 'string', + value: 'Telemetry Name: BAKE_STATE', + }, + ], + metadata: { simulatedActivityId: simulatedActivityId2 }, + stem: 'ECHO', + time: { type: 'COMMAND_COMPLETE' }, + type: 'command', + }, + { + args: [ + { + name: 'echo_string', + type: 'string', + value: 'Parameter Name: BANANA_COLOR_RATE', + }, + ], + metadata: { simulatedActivityId: simulatedActivityId2 }, + stem: 'ECHO', + time: { + type: 'COMMAND_COMPLETE', + }, + type: 'command', + }, + ]); + + /** Begin Cleanup */ + await Promise.all([removeSequence(graphqlClient, sequencePk1), removeSequence(graphqlClient, sequencePk2)]); + await removeExpansionRun(graphqlClient, expansionRunPk); + await removeSimulationArtifacts(graphqlClient, simulationArtifactPk); + await Promise.all([ + removeActivityDirective(graphqlClient, activityId1, planId), + removeActivityDirective(graphqlClient, activityId2, planId), + ]); + await removeExpansionSet(graphqlClient, expansionSetId); + await removeExpansion(graphqlClient, expansionId5); + /** End Cleanup */ + }, 30000); }); it('should provide start, end, and computed attributes on activities', async () => { diff --git a/sequencing-server/test/testUtils/CommandDictionary.ts b/sequencing-server/test/testUtils/CommandDictionary.ts deleted file mode 100644 index 39893c1167..0000000000 --- a/sequencing-server/test/testUtils/CommandDictionary.ts +++ /dev/null @@ -1,64 +0,0 @@ -import fs from 'node:fs'; -import { gql, GraphQLClient } from 'graphql-request'; -import { randomUUID } from 'node:crypto'; -import type { CommandDictionary } from '@nasa-jpl/aerie-ampcs'; - -export const commandDictionaryString = fs.readFileSync( - new URL('../../cdict/command_banananation.xml', import.meta.url).pathname, - 'utf-8', -); - -export async function insertCommandDictionary(graphqlClient: GraphQLClient): Promise<{ - id: number; - dictionary_path: string; - mission: string; - version: string; - parsed_json: CommandDictionary; -}> { - const res = await graphqlClient.request<{ - uploadDictionary: { - id: number; - dictionary_path: string; - mission: string; - version: string; - parsed_json: CommandDictionary; - }; - }>( - gql` - mutation PutCommandDictionary($dictionary: String!, $type: String!) { - uploadDictionary(dictionary: $dictionary, type: $type) { - id - dictionary_path - mission - version - parsed_json - } - } - `, - { - // Generate a UUID for the command dictionary name and version to avoid conflicts when testing. - dictionary: commandDictionaryString.replace(/(Banana Nation|1.0.0.0)/g, randomUUID()), - type: 'COMMAND', - }, - ); - - return res.uploadDictionary; -} - -export async function removeCommandDictionary( - graphqlClient: GraphQLClient, - commandDictionaryId: number, -): Promise { - return graphqlClient.request( - gql` - mutation DeleteCommandDictionary($commandDictionaryId: Int!) { - delete_command_dictionary_by_pk(id: $commandDictionaryId) { - id - } - } - `, - { - commandDictionaryId, - }, - ); -} diff --git a/sequencing-server/test/testUtils/Dictionary.ts b/sequencing-server/test/testUtils/Dictionary.ts new file mode 100644 index 0000000000..08d91f91d2 --- /dev/null +++ b/sequencing-server/test/testUtils/Dictionary.ts @@ -0,0 +1,142 @@ +import fs from 'node:fs'; +import { gql, GraphQLClient } from 'graphql-request'; +import { randomUUID } from 'node:crypto'; +import type { CommandDictionary, ChannelDictionary, ParameterDictionary } from '@nasa-jpl/aerie-ampcs'; +import { DictionaryType } from '../../src/types/types'; + +export const commandDictionaryString = fs.readFileSync( + new URL('../../cdict/command_banananation.xml', import.meta.url).pathname, + 'utf-8', +); +export const channelDictionaryString = fs.readFileSync( + new URL('../../cdict/channel_banananation.xml', import.meta.url).pathname, + 'utf-8', +); + +export const parameterDictionaryString = fs.readFileSync( + new URL('../../cdict/parameter_banananation.xml', import.meta.url).pathname, + 'utf-8', +); + +export async function insertDictionary( + graphqlClient: GraphQLClient, + type: DictionaryType, +): Promise<{ + id: number; + dictionary_path: string; + mission: string; + version: string; + parsed_json: CommandDictionary | ChannelDictionary | ParameterDictionary; +}> { + let dictonaryString = commandDictionaryString; + switch (type) { + case DictionaryType.CHANNEL: + dictonaryString = channelDictionaryString; + break; + case DictionaryType.PARAMETER: + dictonaryString = parameterDictionaryString; + break; + } + const res = await graphqlClient.request<{ + uploadDictionary: { + id: number; + dictionary_path: string; + mission: string; + version: string; + parsed_json: CommandDictionary | ChannelDictionary | ParameterDictionary; + }; + }>( + gql` + mutation PutDictionary($dictionary: String!, $type: String!) { + uploadDictionary(dictionary: $dictionary, type: $type) { + id + dictionary_path + mission + version + parsed_json + } + } + `, + { + // Generate a UUID for the command dictionary name and version to avoid conflicts when testing. + dictionary: dictonaryString.replace(/(Banana Nation|1.0.0.0|1.0.0.1)/g, randomUUID()), + type, + }, + ); + + return res.uploadDictionary; +} + +export async function getDictionary( + graphqlClient: GraphQLClient, + dictionaryId: number, + type: DictionaryType, +): Promise<{ + id: number; + dictionary_path: string; + mission: string; + version: string; + parsed_json: CommandDictionary | ChannelDictionary | ParameterDictionary; +}> { + let dictonaryString = 'command_dictionary_by_pk'; + switch (type) { + case DictionaryType.CHANNEL: + dictonaryString = 'channel_dictionary_by_pk'; + break; + case DictionaryType.PARAMETER: + dictonaryString = 'parameter_dictionary_by_pk'; + break; + } + const res = await graphqlClient.request( + gql` + query GetDictionary($dictionaryId: Int!) { + ${dictonaryString}(id: $dictionaryId) { + id + dictionary_path + mission + version + parsed_json + } + } + `, + { + dictionaryId, + }, + ); + switch (type) { + case DictionaryType.COMMAND: + return res.command_dictionary_by_pk; + case DictionaryType.CHANNEL: + return res.channel_dictionary_by_pk; + case DictionaryType.PARAMETER: + return res.parameter_dictionary_by_pk; + } +} + +export async function removeDictionary( + graphqlClient: GraphQLClient, + dictionaryId: number, + type: DictionaryType, +): Promise { + let mutationString: string = 'delete_command_dictionary_by_pk'; + switch (type) { + case DictionaryType.CHANNEL: + mutationString = 'delete_channel_dictionary_by_pk'; + break; + case DictionaryType.PARAMETER: + mutationString = 'delete_parameter_dictionary_by_pk'; + break; + } + return graphqlClient.request( + gql` + mutation DeleteCommandDictionary($dictionaryId: Int!) { + ${mutationString}(id: $dictionaryId) { + id + } + } + `, + { + dictionaryId, + }, + ); +} diff --git a/sequencing-server/test/testUtils/Expansion.ts b/sequencing-server/test/testUtils/Expansion.ts index 9e26467fd5..5b43cf6e9e 100644 --- a/sequencing-server/test/testUtils/Expansion.ts +++ b/sequencing-server/test/testUtils/Expansion.ts @@ -29,6 +29,35 @@ export async function insertExpansion( return res.addCommandExpansionTypeScript.id; } +export async function getExpansion( + graphqlClient: GraphQLClient, + expansionId: number, +): Promise<{ + id: number; + expansion_logic: string; + name: string; + parcel_id: number; +}> { + const res = await graphqlClient.request<{ + expansion_rule_by_pk: any; + }>( + gql` + query GetExpansion($expansionId: Int!) { + expansion_rule_by_pk(id: $expansionId) { + id + expansion_logic + name + parcel_id + } + } + `, + { + expansionId, + }, + ); + return res.expansion_rule_by_pk; +} + export async function removeExpansion(graphqlClient: GraphQLClient, expansionId: number): Promise { return graphqlClient.request( gql` @@ -85,13 +114,29 @@ export async function insertExpansionSet( return res.createExpansionSet.id; } -export async function getExpansionSet(graphqlClient: GraphQLClient, expansionSetId: number): Promise { - return graphqlClient.request( +export async function getExpansionSet( + graphqlClient: GraphQLClient, + expansionSetId: number, +): Promise<{ + id: number; + name: string; + description: string; + mission_model_id: number; + parcel_id: number; + expansion_rules: { id: number }[]; +} | null> { + const res = await graphqlClient.request( gql` - query GetExpansionRule($expansionSetId: Int!) { + query GetExpansionSet($expansionSetId: Int!) { expansion_set_by_pk(id: $expansionSetId) { + id name description + mission_model_id + parcel_id + expansion_rules { + id + } } } `, @@ -99,6 +144,8 @@ export async function getExpansionSet(graphqlClient: GraphQLClient, expansionSet expansionSetId, }, ); + + return res.expansion_set_by_pk; } export async function removeExpansionSet(graphqlClient: GraphQLClient, expansionSetId: number): Promise { diff --git a/sequencing-server/test/testUtils/Parcel.ts b/sequencing-server/test/testUtils/Parcel.ts index 8f916f5246..5e1a260e1e 100644 --- a/sequencing-server/test/testUtils/Parcel.ts +++ b/sequencing-server/test/testUtils/Parcel.ts @@ -2,7 +2,9 @@ import { gql, GraphQLClient } from 'graphql-request'; export async function insertParcel( graphqlClient: GraphQLClient, - dictionaryID: number, + commandDictionaryID: number, + channelDictionaryID: number, + parameterDictionaryID: number, parcelName: string, ): Promise<{ parcelId: number; @@ -15,8 +17,20 @@ export async function insertParcel( }; }>( gql` - mutation PutParcel($dictionaryID: Int!, $parcelName: String!) { - insert_parcel(objects: { command_dictionary_id: $dictionaryID, name: $parcelName }) { + mutation PutParcel( + $commandDictionaryID: Int! + $channelDictionaryID: Int + $parameterDictionaryID: Int + $parcelName: String! + ) { + insert_parcel( + objects: { + command_dictionary_id: $commandDictionaryID + channel_dictionary_id: $channelDictionaryID + parameter_dictionaries: { data: { parameter_dictionary_id: $parameterDictionaryID } } + name: $parcelName + } + ) { returning { id } @@ -25,7 +39,9 @@ export async function insertParcel( `, { // Generate a UUID for the command dictionary name and version to avoid conflicts when testing. - dictionaryID, + commandDictionaryID, + channelDictionaryID, + parameterDictionaryID, parcelName, }, ); @@ -33,6 +49,39 @@ export async function insertParcel( return { parcelId: res.insert_parcel.returning[0]?.id ?? -1 }; } +export async function getParcel( + graphqlClient: GraphQLClient, + parcelId: number, +): Promise<{ + id: number; + name: string; +} | null> { + const res = await graphqlClient.request<{ + parcel_by_pk: { + id: number; + name: string; + }; + }>( + gql` + query GetParcel($parcelId: Int!) { + parcel_by_pk(id: $parcelId) { + id + command_dictionary_id + channel_dictionary_id + parameter_dictionaries { + parameter_dictionary_id + } + name + } + } + `, + { + parcelId, + }, + ); + return res.parcel_by_pk; +} + export async function removeParcel(graphqlClient: GraphQLClient, parcelId: number): Promise { return graphqlClient.request( gql`