public void ConvertFromTest()
        {
            // Arrange
            SetupCalls();
            var socSkillsMatrixConverter =
                new SocCodeConverter(fakeDynamicContentExtensions);

            //Act
            socSkillsMatrixConverter.ConvertFrom(fakeDynamicContentItem);

            //Assert
            A.CallTo(() => fakeDynamicContentExtensions.GetFieldValue <Lstring>(A <DynamicContent> ._, A <string> ._))
            .MustHaveHappened(4, Times.Exactly);
        }
        static async Task Main(string[] args)
        {
            string timestamp = $"{DateTime.UtcNow:O}";

            IConfigurationRoot config = new ConfigurationBuilder()
                                        .AddJsonFile($"appsettings.Development.json", optional: true)
                                        .Build();

            string jobProfilesToImport = config["JobProfilesToImport"];
            bool   createTestFiles     = bool.Parse(config["CreateTestFiles"] ?? "False");

            string[] socCodeList  = !createTestFiles ? new string[] { } : config["TestSocCodes"].Split(',');
            string[] oNetCodeList = !createTestFiles ? new string[] { } : config["TestONetCodes"].Split(',');
            string[] apprenticeshipStandardsRefList = !createTestFiles ? new string[] { } : config["TestApprenticeshipStandardReferences"].Split(',');
            string   filenamePrefix = createTestFiles ? "TestData_" : "";

            var socCodeConverter  = new SocCodeConverter(socCodeList);
            var socCodeDictionary = socCodeConverter.Go(timestamp);

            using var reader = new StreamReader(@"SeedData\job_profiles_updated.xlsx");
            var jobProfileWorkbook = new XSSFWorkbook(reader.BaseStream);

            var oNetConverter  = new ONetConverter(oNetCodeList);
            var oNetDictionary = oNetConverter.Go(jobProfileWorkbook, timestamp);

            var titleOptionsLookup = new TitleOptionsImporter().Import(jobProfileWorkbook);

            //use these knobs to work around rate - limiting
            const int skip      = 0;
            const int take      = 0;
            const int napTimeMs = 5500;
            // max number of contentitems in an import recipe
            const int batchSize                 = 400;
            const int jobProfileBatchSize       = 200;
            const int occupationLabelsBatchSize = 1000;
            const int occupationsBatchSize      = 400;
            const int skillBatchSize            = 400;
            const int skillLabelsBatchSize      = 1000;

            var httpClient = new HttpClient
            {
                BaseAddress           = new Uri("https://pp.api.nationalcareers.service.gov.uk/job-profiles/"),
                DefaultRequestHeaders =
                {
                    { "version",                   "v1"                                },
                    { "Ocp-Apim-Subscription-Key", config["Ocp-Apim-Subscription-Key"] }
                }
            };

            var dysacImporter = new DysacImporter(oNetConverter.ONetOccupationalCodeToSocCodeDictionary, oNetConverter.ONetOccupationalCodeContentItems);

            using var dysacJobProfileReader = new StreamReader(@"SeedData\dysac_job_profile_mappings.xlsx");
            var dysacJobProfileWorkbook = new XSSFWorkbook(dysacJobProfileReader.BaseStream);

            var client    = new RestHttpClient.RestHttpClient(httpClient);
            var converter = new JobProfileConverter(client, socCodeDictionary, oNetDictionary, titleOptionsLookup, timestamp);
            await converter.Go(skip, take, napTimeMs, jobProfilesToImport);

            var jobProfiles = converter.JobProfiles.ToArray();

            List <string> mappedOccupationUris = new EscoJobProfileMapper().Map(jobProfiles);

            var jobCategoryImporter = new JobCategoryImporter();

            jobCategoryImporter.Import(jobProfileWorkbook, timestamp, jobProfiles);

            var qcfLevelBuilder = new QCFLevelBuilder();

            qcfLevelBuilder.Build(timestamp);

            var apprenticeshipStandardImporter = new ApprenticeshipStandardImporter(apprenticeshipStandardsRefList);

            apprenticeshipStandardImporter.Import(jobProfileWorkbook, timestamp, qcfLevelBuilder.QCFLevelDictionary, jobProfiles);

            using var dysacReader = new StreamReader(@"SeedData\dysac.xlsx");
            var dysacWorkbook = new XSSFWorkbook(dysacReader.BaseStream);

            dysacImporter.ImportONetSkillRank(jobProfileWorkbook);
            dysacImporter.ImportTraits(jobCategoryImporter.JobCategoryContentItemIdDictionary, dysacWorkbook, timestamp);
            dysacImporter.ImportShortQuestions(dysacWorkbook, timestamp);
            dysacImporter.ImportQuestionSet(timestamp);
            dysacImporter.BuildONetOccupationalSkills(dysacJobProfileWorkbook);

            const string cypherCommandRecipesPath = "CypherCommandRecipes";

            string whereClause           = "";
            string occupationMatch       = "";
            int    totalOccupations      = 2942;
            int    totalOccupationLabels = int.Parse(config["totalOccupationLabels"] ?? "33036");
            int    totalSkillLabels      = int.Parse(config["totalSkillLabels"] ?? "97816");
            int    totalSkills           = int.Parse(config["totalSkills"] ?? "13485");

            if (!string.IsNullOrWhiteSpace(jobProfilesToImport) && jobProfilesToImport != "*")
            {
                string uriList = string.Join(',', mappedOccupationUris.Select(u => $"'{u}'"));
                whereClause      = $"where o.uri in [{uriList}]";
                totalOccupations = mappedOccupationUris.Count;
                occupationMatch  = " (o:esco__Occupation) --> ";
            }
            IDictionary <string, string> tokens = new Dictionary <string, string>
            {
                { "whereClause", whereClause },
                { "occupationMatch", occupationMatch }
            };

            NewMasterRecipe("main");

            bool excludeGraphContentMutators = bool.Parse(config["ExcludeGraphContentMutators"] ?? "False");

            if (!(excludeGraphContentMutators || createTestFiles))
            {
                await CopyRecipeWithTokenisation(cypherCommandRecipesPath, "CreateOccupationLabelNodes", tokens);
                await CopyRecipeWithTokenisation(cypherCommandRecipesPath, "CreateOccupationPrefLabelNodes", tokens);
                await CopyRecipeWithTokenisation(cypherCommandRecipesPath, "CreateSkillLabelNodes", tokens);
                await CopyRecipe(cypherCommandRecipesPath, "CleanUpEscoData");
            }

            bool excludeGraphIndexMutators = bool.Parse(config["ExcludeGraphIndexMutators"] ?? "False");

            if (!(excludeGraphIndexMutators || createTestFiles))
            {
                await CopyRecipe(cypherCommandRecipesPath, "CreateFullTextSearchIndexes");
            }

            const string cypherToContentRecipesPath = "CypherToContentRecipes";

            await BatchRecipes(cypherToContentRecipesPath, "CreateOccupationLabelContentItems", occupationLabelsBatchSize, "OccupationLabels", totalOccupationLabels, tokens);
            await BatchRecipes(cypherToContentRecipesPath, "CreateSkillLabelContentItems", skillLabelsBatchSize, "SkillLabels", totalSkillLabels, tokens);

            await BatchRecipes(cypherToContentRecipesPath, "CreateSkillContentItems", skillBatchSize, "Skills", totalSkills, tokens);
            await BatchRecipes(cypherToContentRecipesPath, "CreateOccupationContentItems", occupationsBatchSize, "Occupations", totalOccupations, tokens);

            ProcessJobProfileSpreadsheet(jobProfileWorkbook);

            converter.UpdateRouteItemsWithSharedNames();



            // remove calls to pages related recipe file generation
            // comment out as may need to re-generate later

            const string contentRecipesPath = "ContentRecipes";

            await BatchSerializeToFiles(qcfLevelBuilder.QCFLevelContentItems, batchSize, $"{filenamePrefix}QCFLevels");
            await BatchSerializeToFiles(apprenticeshipStandardImporter.ApprenticeshipStandardRouteContentItems, batchSize, $"{filenamePrefix}ApprenticeshipStandardRoutes");
            await BatchSerializeToFiles(apprenticeshipStandardImporter.ApprenticeshipStandardContentItems, batchSize, $"{filenamePrefix}ApprenticeshipStandards");
            await BatchSerializeToFiles(RouteFactory.RequirementsPrefixes.IdLookup.Select(r => new RequirementsPrefixContentItem(r.Key, timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}RequirementsPrefixes");
            await BatchSerializeToFiles(converter.ApprenticeshipRoutes.Links.IdLookup.Select(r => new ApprenticeshipLinkContentItem(GetTitle("ApprenticeshipLink", r.Key), r.Key, timestamp, r.Value)), batchSize, $"{filenamePrefix}ApprenticeshipLinks");
            await BatchSerializeToFiles(converter.ApprenticeshipRoutes.Requirements.IdLookup.Select(r => new ApprenticeshipRequirementContentItem(GetTitle("ApprenticeshipRequirement", r.Key), timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}ApprenticeshipRequirements");
            await BatchSerializeToFiles(converter.CollegeRoutes.Links.IdLookup.Select(r => new CollegeLinkContentItem(GetTitle("CollegeLink", r.Key), r.Key, timestamp, r.Value)), batchSize, $"{filenamePrefix}CollegeLinks");
            await BatchSerializeToFiles(converter.CollegeRoutes.Requirements.IdLookup.Select(r => new CollegeRequirementContentItem(GetTitle("CollegeRequirement", r.Key), timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}CollegeRequirements");
            await BatchSerializeToFiles(converter.UniversityRoutes.Links.IdLookup.Select(r => new UniversityLinkContentItem(GetTitle("UniversityLink", r.Key), r.Key, timestamp, r.Value)), batchSize, $"{filenamePrefix}UniversityLinks");
            await BatchSerializeToFiles(converter.UniversityRoutes.Requirements.IdLookup.Select(r => new UniversityRequirementContentItem(GetTitle("UniversityRequirement", r.Key), timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}UniversityRequirements");
            await BatchSerializeToFiles(converter.DayToDayTasks.Select(x => new DayToDayTaskContentItem(x.Key, timestamp, x.Key, x.Value.id)), batchSize, $"{filenamePrefix}DayToDayTasks");
            await BatchSerializeToFiles(converter.OtherRequirements.IdLookup.Select(r => new OtherRequirementContentItem(r.Key, timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}OtherRequirements");
            await BatchSerializeToFiles(converter.Registrations.IdLookup.Select(r => new RegistrationContentItem(GetTitle("Registration", r.Key), timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}Registrations");
            await BatchSerializeToFiles(converter.Restrictions.IdLookup.Select(r => new RestrictionContentItem(GetTitle("Restriction", r.Key), timestamp, r.Key, r.Value)), batchSize, $"{filenamePrefix}Restrictions");
            await BatchSerializeToFiles(socCodeConverter.SocCodeContentItems, batchSize, $"{filenamePrefix}SocCodes");

            await CopyRecipe(contentRecipesPath, "ONetSkill");
            await BatchSerializeToFiles(oNetConverter.ONetOccupationalCodeContentItems, batchSize, $"{filenamePrefix}ONetOccupationalCodes", CSharpContentStep.StepName);

            await CopyRecipeWithTokenisation(cypherCommandRecipesPath, "ONetSkillMappings", new Dictionary <string, string>
            {
                { "commandText", string.Join($"{Environment.NewLine},", dysacImporter.ONetSkillCypherCommands) }
            });

            await BatchSerializeToFiles(converter.WorkingEnvironments.IdLookup.Select(x => new WorkingEnvironmentContentItem(GetTitle("Environment", x.Key), timestamp, x.Key, x.Value)), batchSize, $"{filenamePrefix}WorkingEnvironments");
            await BatchSerializeToFiles(converter.WorkingLocations.IdLookup.Select(x => new WorkingLocationContentItem(GetTitle("Location", x.Key), timestamp, x.Key, x.Value)), batchSize, $"{filenamePrefix}WorkingLocations");
            await BatchSerializeToFiles(converter.WorkingUniforms.IdLookup.Select(x => new WorkingUniformContentItem(GetTitle("Uniform", x.Key), timestamp, x.Key, x.Value)), batchSize, $"{filenamePrefix}WorkingUniforms");

            await BatchSerializeToFiles(converter.ApprenticeshipRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}ApprenticeshipRoutes");
            await BatchSerializeToFiles(converter.CollegeRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}CollegeRoutes");
            await BatchSerializeToFiles(converter.UniversityRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}UniversityRoutes");
            await BatchSerializeToFiles(converter.DirectRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}DirectRoutes");
            await BatchSerializeToFiles(converter.OtherRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}OtherRoutes");
            await BatchSerializeToFiles(converter.VolunteeringRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}VolunteeringRoutes");
            await BatchSerializeToFiles(converter.WorkRoute.ItemToCompositeName.Keys, batchSize, $"{filenamePrefix}WorkRoutes");

            await BatchSerializeToFiles(jobProfiles, jobProfileBatchSize, $"{filenamePrefix}JobProfiles", CSharpContentStep.StepName);
            await BatchSerializeToFiles(jobCategoryImporter.JobCategoryContentItems, batchSize, $"{filenamePrefix}JobCategories");

            await BatchSerializeToFiles(dysacImporter.PersonalityTraitContentItems, batchSize, $"{filenamePrefix}PersonalityTrait", CSharpContentStep.StepName);
            await BatchSerializeToFiles(dysacImporter.PersonalityShortQuestionContentItems, batchSize, $"{filenamePrefix}PersonalityShortQuestion", CSharpContentStep.StepName);
            await BatchSerializeToFiles(dysacImporter.PersonalityQuestionSetContentItems, batchSize, $"{filenamePrefix}PersonalityQuestionSet", CSharpContentStep.StepName);

            await CopyRecipe(contentRecipesPath, "PersonalityFilteringQuestion");

            string masterRecipeName = config["MasterRecipeName"] ?? "master";

            await WriteMasterRecipesFiles(masterRecipeName);

            await File.WriteAllTextAsync($"{OutputBasePath}content items count_{_executionId}.txt", @$ "{_importFilesReport}# Totals
    {_importTotalsReport}");