Path: blob/main/tests/unit/confluence/confluence.test.ts
6451 views
/*1*2* Copyright (C) 2020 by RStudio, PBC3*4*/5import { unitTest } from "../../test.ts";6import { assertEquals, assertThrows } from "testing/asserts";78import {9buildContentCreate,10buildFileToMetaTable,11buildPublishRecordForContent,12buildSpaceChanges,13capitalizeFirstLetter,14confluenceParentFromString,15convertForSecondPass,16FILE_FINDER,17filterFilesForUpdate,18findAttachments,19findPagesToDelete,20flattenIndexes,21footnoteTransform,22getMessageFromAPIError,23getNextVersion,24getTitle,25isNotFound,26isUnauthorized,27LINK_FINDER,28mergeSitePages,29tokenFilterOut,30transformAtlassianDomain,31updateImagePaths,32updateLinks,33validateEmail,34validateServer,35validateToken,36wrapBodyForConfluence,37writeTokenComparator,38} from "../../../src/publish/confluence/confluence-helper.ts";39import { ApiError, PublishRecord } from "../../../src/publish/types.ts";40import {41AccountToken,42AccountTokenType,43InputMetadata,44} from "../../../src/publish/provider-types.ts";45import {46ConfluenceParent,47ConfluenceSpaceChange,48Content,49ContentBody,50ContentChangeType,51ContentCreate,52ContentStatusEnum,53ContentSummary,54ContentUpdate,55ContentVersion,56ExtractedLink,57PAGE_TYPE,58SiteFileMetadata,59SitePage,60Space,61} from "../../../src/publish/confluence/api/types.ts";6263const RUN_ALL_TESTS = true;64const FOCUS_TEST = false;65const HIDE_NOISE = false;6667const xtest = (68name: string,69ver: () => Promise<unknown> // VoidFunction,70) => {};71const test = FOCUS_TEST ? xtest : unitTest;72const otest = unitTest;7374const buildFakeContent = (): Content => {75return {76id: "fake-id",77type: "fake-type",78status: "current",79title: "fake-title",80space: {81key: "fake-space-key",82id: "fake-space-id",83homepage: {84id: "fake-space-id",85title: "fake-space-title",86},87},88version: {89number: 1,90},91ancestors: null,92descendants: null,93body: {94storage: {95value: "fake-body",96representation: "raw",97},98},99};100};101102const FAKE_PARENT: ConfluenceParent = {103space: "QUARTOCONF",104parent: "8781825",105};106107const runGeneralTests = () => {108test("transformAtlassianDomain_basic", async () => {109const result = transformAtlassianDomain("fake-domain");110const expected = "https://fake-domain.atlassian.net/";111assertEquals(expected, result);112});113114test("transformAtlassianDomain_EmptyString", async () => {115const result = transformAtlassianDomain("");116const expected = "https://.atlassian.net/";117assertEquals(expected, result);118});119120test("transformAtlassianDomain_addTrailing", async () => {121const result = transformAtlassianDomain("https://something");122const expected = "https://something/";123assertEquals(expected, result);124});125126test("transformAtlassianDomain_partialPrefix", async () => {127const result = transformAtlassianDomain("htt://something");128const expected = "https://htt://something.atlassian.net/";129assertEquals(expected, result);130});131132test("transformAtlassianDomain_addPrefixAndTrailing", async () => {133const result = transformAtlassianDomain("something");134const expected = "https://something.atlassian.net/";135assertEquals(expected, result);136});137138test("validateServer_empty", async () => {139const toCall = () => validateServer("");140assertThrows(toCall, "");141});142143test("validateServer_valid", async () => {144const result = validateServer("fake-domain");145const expected = true;146assertEquals(expected, result);147});148149test("validateServer_invalid", async () => {150const result = validateServer("_!@ ... #");151const expected = "Not a valid URL";152assertEquals(expected, result);153});154155test("validateName_empty", async () => {156const toCall = () => validateEmail("");157assertThrows(toCall, "");158});159160test("validateName_valid", async () => {161const result = validateEmail("[email protected]");162const expected = true;163assertEquals(expected, result);164});165166test("validateName_invalid_JustName", async () => {167const result = validateEmail("al.manning");168const expected = "Invalid email address";169assertEquals(expected, result);170});171172test("validateToken_empty", async () => {173const toCall = () => validateToken("");174assertThrows(toCall, "");175});176177test("getMessageFromAPIError_null", async () => {178const result = getMessageFromAPIError(null);179const expected = "Unknown error";180assertEquals(expected, result);181});182183test("getMessageFromAPIError_emptyString", async () => {184const result = getMessageFromAPIError("");185const expected = "Unknown error";186assertEquals(expected, result);187});188189test("getMessageFromAPIError_APIError", async () => {190const result = getMessageFromAPIError(new ApiError(123, "status-text"));191const expected = "123 - status-text";192assertEquals(expected, result);193});194195test("tokenFilterOut_sameToken", async () => {196const fakeToken = {197type: AccountTokenType.Environment,198name: "fake-name",199server: "fake-server",200token: "fake-token",201};202203const result = tokenFilterOut(fakeToken, fakeToken);204const expected = false;205assertEquals(expected, result);206});207208test("tokenFilterOut_differentToken", async () => {209const fakeToken = {210type: AccountTokenType.Environment,211name: "fake-name",212server: "fake-server",213token: "fake-token",214};215216const fakeToken2 = {217type: AccountTokenType.Environment,218name: "fake-name2",219server: "fake-server2",220token: "fake-token2",221};222223const result = tokenFilterOut(fakeToken, fakeToken2);224const expected = true;225assertEquals(expected, result);226});227228test("isUnauthorized_EmptyError", async () => {229const result = isUnauthorized(new Error());230const expected = false;231assertEquals(expected, result);232});233234test("isUnauthorized_401", async () => {235const result = isUnauthorized(new ApiError(401, "fake-status"));236const expected = true;237assertEquals(expected, result);238});239240test("isUnauthorized_403", async () => {241const result = isUnauthorized(new ApiError(403, "fake-status"));242const expected = true;243assertEquals(expected, result);244});245246test("isNotFound_Empty", async () => {247const result = isNotFound(new Error());248const expected = false;249assertEquals(expected, result);250});251252test("isNotFound_404", async () => {253const result = isNotFound(new ApiError(404, "fake-status"));254const expected = true;255assertEquals(expected, result);256});257};258259const runConfluenceParentFromString = () => {260test("confluenceParentFromString_empty", async () => {261const result = confluenceParentFromString("");262const expected = {263space: "",264parent: "",265};266assertEquals(expected, result);267});268269test("confluenceParentFromString_valid", async () => {270const url =271"https://allenmanning.atlassian.net/wiki/spaces/QUARTOCONF/pages/8781825/Markdown+Basics1";272const result = confluenceParentFromString(url);273const expected: ConfluenceParent = {274space: "QUARTOCONF",275parent: "8781825",276};277assertEquals(expected, result);278});279280test("confluenceParentFromString_valid_noParent", async () => {281const url = "https://allenmanning.atlassian.net/wiki/spaces/QUARTOCONF";282const result = confluenceParentFromString(url);283const expected: ConfluenceParent = {284space: "QUARTOCONF",285parent: undefined,286};287assertEquals(expected, result);288});289290test("confluenceParentFromString_spaces_overview", async () => {291const url =292"https://allenmanning.atlassian.net/wiki/spaces/~557058634d59d0949841909bb13093ab41d0c5/overview";293const result = confluenceParentFromString(url);294const expected: ConfluenceParent = {295space: "~557058634d59d0949841909bb13093ab41d0c5",296parent: undefined,297};298assertEquals(expected, result);299});300301test("confluenceParentFromString_valid_spaces_pages", async () => {302const url =303"https://rstudiopbc-sandbox-249.atlassian.net/wiki/spaces/~62d7a66910c44eb6e3218195/pages/43122955";304const result = confluenceParentFromString(url);305const expected: ConfluenceParent = {306space: "~62d7a66910c44eb6e3218195",307parent: "43122955",308};309assertEquals(expected, result);310});311312test("confluenceParentFromString_valid_spaces_pages_with_homepage", async () => {313const url =314"https://allenmanning.atlassian.net/wiki/spaces/~557058634d59d0949841909bb13093ab41d0c5/overview?homepageId=65617";315const result = confluenceParentFromString(url);316const expected: ConfluenceParent = {317space: "~557058634d59d0949841909bb13093ab41d0c5",318parent: undefined,319};320assertEquals(expected, result);321});322323test("confluenceParentFromString_valid_noParent", async () => {324const url = "https://test.atlassian.net/wiki/spaces/QUARTOCONF";325const result = confluenceParentFromString(url);326const expected: ConfluenceParent = {327space: "QUARTOCONF",328parent: undefined,329};330assertEquals(expected, result);331});332333test("confluenceParentFromString_valid_space_dots", async () => {334// https://github.com/quarto-dev/quarto-cli/issues/5405335const url = "https://test.atlassian.net/wiki/spaces/~brian.smith/pages/126583477";336const result = confluenceParentFromString(url);337const expected: ConfluenceParent = {338space: "~brian.smith",339parent: "126583477",340};341assertEquals(expected, result);342});343344test("confluenceParentFromString_invalid_noSpace", async () => {345const url = "https://test.atlassian.net/QUARTOCONF";346const result = confluenceParentFromString(url);347const expected = {348space: "",349parent: "",350};351assertEquals(expected, result);352});353354test("wrapBodyForConfluence_empty", async () => {355const value = "";356const result = wrapBodyForConfluence(value);357const expected = {358storage: {359value: "",360representation: "storage",361},362};363assertEquals(expected, result);364});365};366367const runPublishRecordTests = () => {368const fakeServer = "https://test.atlassian.net";369370const checkForContent = (371expectedURL: string,372expectedId: string,373server: string = fakeServer,374content: Content = buildFakeContent()375) => {376const result = buildPublishRecordForContent(server, content);377const expectedPublishRecord: PublishRecord = {378id: expectedId,379url: expectedURL,380};381const url: URL = new URL(expectedURL);382const expected: [PublishRecord, URL] = [expectedPublishRecord, url];383384assertEquals(expected[0], result[0]);385assertEquals(expected[1], result[1]);386};387388const checkThrows = (389server: string = fakeServer,390content: Content = buildFakeContent()391) => {392assertThrows(() => buildPublishRecordForContent(server, content));393};394395test("buildPublishRecord_validWithChecker", async () => {396const expectedURL =397"https://test.atlassian.net/wiki/spaces/fake-space-key/pages/fake-id";398const expectedId = "fake-id";399400checkForContent(expectedURL, expectedId);401});402403test("buildPublishRecord_noIdThrows", async () => {404const fakeContent = buildFakeContent();405fakeContent.id = null;406checkThrows(fakeServer, fakeContent);407});408409test("buildPublishRecord_noSpaceThrows", async () => {410const fakeContent = buildFakeContent();411fakeContent.space = null;412checkThrows(fakeServer, fakeContent);413});414415test("buildPublishRecord_emptyServerThrows", async () => {416checkThrows("");417});418419const checkForParent = (420expectedURL: string,421expectedId: string,422server: string = fakeServer,423content: Content = buildFakeContent()424) => {425const result = buildPublishRecordForContent(server, content);426const expectedPublishRecord: PublishRecord = {427id: expectedId,428url: expectedURL,429};430const url: URL = new URL(expectedURL);431const expected: [PublishRecord, URL] = [expectedPublishRecord, url];432433assertEquals(expected[0], result[0]);434assertEquals(expected[1], result[1]);435};436};437438const runGetNextVersionTests = () => {439const suiteLabel = (label: string) => `GetNextVersionTests_${label}`;440441const check = (previousPage: Content, expected: ContentVersion) => {442const actual = getNextVersion(previousPage);443assertEquals(expected, actual);444};445446test(suiteLabel("1to2"), async () => {447const previousPage: Content = buildFakeContent();448const expected: ContentVersion = { number: 2 };449check(previousPage, expected);450});451452test(suiteLabel("Nullto1"), async () => {453const previousPage: Content = buildFakeContent();454previousPage.version = null;455const expected: ContentVersion = { number: 1 };456check(previousPage, expected);457});458};459460const runWriteTokenComparator = () => {461const suiteLabel = (label: string) => `WriteTokenComparator_${label}`;462463const check = (464aToken: AccountToken,465bToken: AccountToken,466expected: boolean467) => {468const actual = writeTokenComparator(aToken, bToken);469assertEquals(expected, actual);470};471472test(suiteLabel("allNotEqual"), async () => {473check(474{475server: "a-server",476name: "a-name",477type: AccountTokenType.Authorized,478token: "fake-token-a",479},480{481server: "b-server",482name: "b-name",483type: AccountTokenType.Environment,484token: "fake-token-b",485},486false487);488});489490test(suiteLabel("nameNotEqual"), async () => {491check(492{493server: "a-server",494name: "different-a-name",495type: AccountTokenType.Authorized,496token: "fake-token-a",497},498{499server: "a-server",500name: "a-name",501type: AccountTokenType.Authorized,502token: "fake-token-a",503},504false505);506});507508test(suiteLabel("serverNotEqual"), async () => {509check(510{511server: "different-a-server",512name: "a-name",513type: AccountTokenType.Authorized,514token: "fake-token-a",515},516{517server: "a-server",518name: "a-name",519type: AccountTokenType.Authorized,520token: "fake-token-a",521},522false523);524});525526test(suiteLabel("typeNotEqual"), async () => {527check(528{529server: "a-server",530name: "a-name",531type: AccountTokenType.Environment,532token: "fake-token-a",533},534{535server: "a-server",536name: "a-name",537type: AccountTokenType.Authorized,538token: "fake-token-a",539},540true541);542});543544test(suiteLabel("tokenNotEqual"), async () => {545check(546{547server: "a-server",548name: "a-name",549type: AccountTokenType.Authorized,550token: "differet-fake-token-a",551},552{553server: "a-server",554name: "a-name",555type: AccountTokenType.Authorized,556token: "fake-token-a",557},558true559);560});561};562563const runFilterFilesForUpdate = () => {564const suiteLabel = (label: string) => `FilterFilesForUpdate_${label}`;565566const check = (allFiles: string[], expected: string[]) => {567const actual = filterFilesForUpdate(allFiles);568assertEquals(expected, actual);569};570571test(suiteLabel("noFiles"), async () => {572check([], []);573});574575test(suiteLabel("flatMixed"), async () => {576const fakeFileList = [577"knowledge-base.html",578"team.xml",579"agreements.html",580"mission.xml",581"ci-log.html",582];583const expected = ["team.xml", "mission.xml"];584585check(fakeFileList, expected);586});587588test(suiteLabel("nestedMixed"), async () => {589const fakeFileList = [590"parent/child.xml",591"knowledge-base.html",592"team.xml",593"agreements.html",594"mission.xml",595"ci-log.html",596];597const expected = ["parent/child.xml", "team.xml", "mission.xml"];598599check(fakeFileList, expected);600});601};602603const runBuildContentCreate = () => {604const suiteLabel = (label: string) => `BuildContentCreate_${label}`;605606test(suiteLabel("minParams"), async () => {607const expected: ContentCreate = {608contentChangeType: ContentChangeType.create,609fileName: "fake-file-name",610title: "fake-title",611type: PAGE_TYPE,612space: {613key: "fake-space-key",614id: "fake-space-id",615homepage: buildFakeContent(),616},617status: ContentStatusEnum.current,618ancestors: null,619body: {620storage: {621value: "fake-value",622representation: "storage",623},624},625};626const fakeSpace: Space = {627key: "fake-space-key",628id: "fake-space-id",629homepage: buildFakeContent(),630};631const fakeBody: ContentBody = {632storage: {633value: "fake-value",634representation: "storage",635},636};637const actual: ContentCreate = buildContentCreate(638"fake-title",639fakeSpace,640fakeBody,641"fake-file-name"642);643644assertEquals(expected, actual);645});646647test(suiteLabel("allParams"), async () => {648const expected: ContentCreate = {649contentChangeType: ContentChangeType.create,650fileName: "fake-filename",651title: "fake-title",652type: "fake-type",653space: {654key: "fake-space-key",655id: "fake-space-id",656homepage: buildFakeContent(),657},658status: ContentStatusEnum.deleted,659ancestors: [660{661id: "fake-parent",662},663],664body: {665storage: {666value: "fake-value",667representation: "storage",668},669},670};671672const fakeSpace: Space = {673key: "fake-space-key",674id: "fake-space-id",675homepage: buildFakeContent(),676};677const fakeBody: ContentBody = {678storage: {679value: "fake-value",680representation: "storage",681},682};683const actual: ContentCreate = buildContentCreate(684"fake-title",685fakeSpace,686fakeBody,687"fake-filename",688"fake-parent",689ContentStatusEnum.deleted,690"fake-id",691"fake-type"692);693694assertEquals(expected, actual);695});696};697698const runGetTitle = () => {699const suiteLabel = (label: string) => `GetTitle_${label}`;700const fakeInputMetadata: Record<string, InputMetadata> = {701"fake-file.qmd": {702title: "fake-title1",703author: "fake-author",704date: "fake-date",705},706"folder/fake-file2.qmd": {707title: "fake-title2",708author: "fake-author2",709date: "fake-date2",710},711};712713test(suiteLabel("valid"), async () => {714const fileName = "fake-file.xml";715const expected = "fake-title1";716const result = getTitle(fileName, fakeInputMetadata);717assertEquals(expected, result);718});719720test(suiteLabel("valid2"), async () => {721const fileName = "folder/fake-file2.xml";722const expected = "fake-title2";723const result = getTitle(fileName, fakeInputMetadata);724assertEquals(expected, result);725});726727test(suiteLabel("no-match"), async () => {728const fileName = "fake-file3.xml";729const expected = "Fake-file3";730const result = getTitle(fileName, fakeInputMetadata);731assertEquals(expected, result);732});733734test(suiteLabel("no-match-empty"), async () => {735const fileName = "";736const expected = "";737const result = getTitle(fileName, fakeInputMetadata);738assertEquals(expected, result);739});740};741742const runMergeSitePages = () => {743const suiteLabel = (label: string) => `MergeSitePages_${label}`;744745test(suiteLabel("basic_valid"), async () => {746const shallowPages: ContentSummary[] = [747{748id: "123",749title: "fake-title",750ancestors: [{ id: "fake-ancestor" }],751},752];753const contentProperties = [754[755{756key: "fake-key",757value: "fake-value",758},759],760];761const expected: SitePage[] = [762{763id: "123",764title: "fake-title",765ancestors: [{ id: "fake-ancestor" }],766metadata: {767["fake-key"]: "fake-value",768},769},770];771const result = mergeSitePages(shallowPages, contentProperties);772assertEquals(expected, result);773});774775test(suiteLabel("basic_valid_2props"), async () => {776const shallowPages: ContentSummary[] = [777{778id: "123",779title: "fake-title",780ancestors: [{ id: "fake-ancestor" }],781},782];783const contentProperties = [784[785{786key: "fake-key",787value: "fake-value",788},789{790key: "fake-key2",791value: "fake-value2",792},793],794];795const expected: SitePage[] = [796{797id: "123",798title: "fake-title",799metadata: {800["fake-key"]: "fake-value",801["fake-key2"]: "fake-value2",802},803ancestors: [{ id: "fake-ancestor" }],804},805];806const result = mergeSitePages(shallowPages, contentProperties);807assertEquals(expected, result);808});809810test(suiteLabel("multiple_valid"), async () => {811const shallowPages: ContentSummary[] = [812{813id: "123",814title: "fake-title",815ancestors: [{ id: "fake-ancestor" }],816},817{818id: "456",819title: "fake-title2",820ancestors: [{ id: "fake-ancestor-2" }],821},822];823const contentProperties = [824[825{826key: "fake-key",827value: "fake-value",828},829],830[831{832key: "fake-key2",833value: "fake-value2",834},835{836key: "fake-key3",837value: "fake-value3",838},839],840];841const expected: SitePage[] = [842{843id: "123",844title: "fake-title",845metadata: {846["fake-key"]: "fake-value",847},848ancestors: [{ id: "fake-ancestor" }],849},850{851id: "456",852title: "fake-title2",853metadata: {854["fake-key2"]: "fake-value2",855["fake-key3"]: "fake-value3",856},857ancestors: [{ id: "fake-ancestor-2" }],858},859];860const result = mergeSitePages(shallowPages, contentProperties);861assertEquals(expected, result);862});863864test(suiteLabel("not_matching"), async () => {865const shallowPages: ContentSummary[] = [866{867id: "123",868title: "fake-title",869ancestors: [{ id: "fake-ancestor" }],870},871{872id: "456",873title: "fake-title2",874ancestors: [{ id: "fake-ancestor-2" }],875},876];877const contentProperties = [878[879{880key: "fake-key",881value: "fake-value",882},883],884];885const expected: SitePage[] = [886{887id: "123",888title: "fake-title",889metadata: {890["fake-key"]: "fake-value",891},892ancestors: [{ id: "fake-ancestor" }],893},894{895id: "456",896title: "fake-title2",897metadata: {},898ancestors: [{ id: "fake-ancestor-2" }],899},900];901const result = mergeSitePages(shallowPages, contentProperties);902assertEquals(expected, result);903});904};905906const runBuildSpaceChanges = () => {907const suiteLabel = (label: string) => `BuildSpaceChanges_${label}`;908909const fakeSpace: Space = {910key: "fake-space-key",911id: "fake-space-id",912homepage: buildFakeContent(),913};914915const fakeFile: SiteFileMetadata = {916fileName: "fake-file-name",917title: "fake-title",918originalTitle: "fake-title-original",919contentBody: {920storage: {921value: "fake-value",922representation: "storage",923},924},925};926927const fakeFileMatchingPage: SiteFileMetadata = {928fileName: "fake-file-name",929title: "fake-title",930originalTitle: "fake-title-original",931contentBody: {932storage: {933value: "fake-value",934representation: "storage",935},936},937};938939const fakeFile2: SiteFileMetadata = {940fileName: "fake-file-name2",941title: "fake-title2",942originalTitle: "fake-title2-original",943contentBody: {944storage: {945value: "fake-value2",946representation: "storage",947},948},949};950951test(suiteLabel("no_files"), async () => {952const fileMetadataList: SiteFileMetadata[] = [];953const expected: ConfluenceSpaceChange[] = [];954const actual: ConfluenceSpaceChange[] = buildSpaceChanges(955fileMetadataList,956FAKE_PARENT,957fakeSpace958);959assertEquals(expected, actual);960});961962test(suiteLabel("one_file"), async () => {963const fileMetadataList: SiteFileMetadata[] = [fakeFile];964const expected: ConfluenceSpaceChange[] = [965{966contentChangeType: ContentChangeType.create,967ancestors: [968{969id: "8781825",970},971],972body: {973storage: {974representation: "storage",975value: "fake-value",976},977},978fileName: "fake-file-name",979space: {980key: "fake-space-key",981id: "fake-space-id",982homepage: buildFakeContent(),983},984status: "current",985title: "fake-title",986type: "page",987},988];989const actual: ConfluenceSpaceChange[] = buildSpaceChanges(990fileMetadataList,991FAKE_PARENT,992fakeSpace993);994assertEquals(expected, actual);995});996997test(suiteLabel("two_files"), async () => {998const fileMetadataList: SiteFileMetadata[] = [fakeFile, fakeFile2];999const expected: ConfluenceSpaceChange[] = [1000{1001contentChangeType: ContentChangeType.create,1002ancestors: [1003{1004id: "8781825",1005},1006],1007body: {1008storage: {1009representation: "storage",1010value: "fake-value",1011},1012},1013fileName: "fake-file-name",1014space: {1015key: "fake-space-key",1016id: "fake-space-id",1017homepage: buildFakeContent(),1018},1019status: "current",1020title: "fake-title",1021type: "page",1022},1023{1024contentChangeType: ContentChangeType.create,1025ancestors: [1026{1027id: "8781825",1028},1029],1030body: {1031storage: {1032representation: "storage",1033value: "fake-value2",1034},1035},1036fileName: "fake-file-name2",1037space: {1038key: "fake-space-key",1039id: "fake-space-id",1040homepage: buildFakeContent(),1041},1042status: "current",1043title: "fake-title2",1044type: "page",1045},1046];1047const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1048fileMetadataList,1049FAKE_PARENT,1050fakeSpace1051);1052assertEquals(expected, actual);1053});10541055test(suiteLabel("one_file_update"), async () => {1056const fileMetadataList: SiteFileMetadata[] = [fakeFile];1057const expected: ConfluenceSpaceChange[] = [1058{1059contentChangeType: ContentChangeType.update,1060ancestors: [1061{1062id: "8781825",1063},1064],1065body: {1066storage: {1067representation: "storage",1068value: "fake-value",1069},1070},1071fileName: "fake-file-name",1072status: "current",1073title: "fake-title",1074type: "page",1075id: "123456",1076version: null,1077},1078];1079const existingSite: SitePage[] = [1080{1081id: "123456",1082title: "fake-title",1083metadata: { fileName: "fake-file-name" },1084},1085];1086const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1087fileMetadataList,1088FAKE_PARENT,1089fakeSpace,1090existingSite1091);1092assertEquals(expected, actual);1093});10941095test(suiteLabel("one_file_update_matching"), async () => {1096const fileMetadataList: SiteFileMetadata[] = [fakeFileMatchingPage];1097const expected: ConfluenceSpaceChange[] = [1098{1099contentChangeType: ContentChangeType.update,1100ancestors: [1101{1102id: "8781825",1103},1104],1105body: {1106storage: {1107representation: "storage",1108value: "fake-value",1109},1110},1111fileName: "fake-file-name",1112status: "current",1113title: "fake-title",1114type: "page",1115id: "123456",1116version: null,1117},1118];1119const existingSite: SitePage[] = [1120{1121id: "123456",1122title: "fake-title",1123metadata: { fileName: "fake-file-name" },1124},1125];1126const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1127fileMetadataList,1128FAKE_PARENT,1129fakeSpace,1130existingSite1131);1132assertEquals(expected, actual);1133});11341135test(suiteLabel("findPagesToDelete"), async () => {1136const fileMetadataList: SiteFileMetadata[] = [fakeFile];1137const existingSite: SitePage[] = [1138{1139id: "fake-file-id",1140title: "fake-title",1141metadata: { fileName: "fake-file-name" },1142ancestors: [],1143},1144{1145id: "delete-me-file-id",1146title: "delete-me-title",1147metadata: { fileName: "delete-me-file-name" },1148ancestors: [],1149},1150];1151const expected = [1152{1153id: "delete-me-file-id",1154title: "delete-me-title",1155metadata: { fileName: "delete-me-file-name" },1156ancestors: [],1157},1158];1159const actual = findPagesToDelete(fileMetadataList, existingSite);1160assertEquals(expected, actual);1161});11621163test(suiteLabel("one_file_delete"), async () => {1164const fileMetadataList: SiteFileMetadata[] = [fakeFile];1165const existingSite: SitePage[] = [1166{1167id: "fake-file-id",1168title: "fake-title",1169metadata: { fileName: "fake-file-name" },1170},1171{1172id: "delete-me-file-id",1173title: "delete-me-title",1174metadata: { fileName: "delete-me-file-name" },1175},1176];1177const expected: ConfluenceSpaceChange[] = [1178{1179id: "delete-me-file-id",1180contentChangeType: ContentChangeType.delete,1181},1182{1183contentChangeType: ContentChangeType.update,1184ancestors: [1185{1186id: "8781825",1187},1188],1189body: {1190storage: {1191representation: "storage",1192value: "fake-value",1193},1194},1195fileName: "fake-file-name",1196status: "current",1197title: "fake-title",1198type: "page",1199id: "fake-file-id",1200version: null,1201},1202];1203const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1204fileMetadataList,1205FAKE_PARENT,1206fakeSpace,1207existingSite1208);1209assertEquals(expected, actual);1210});1211};12121213const runSpaceCreatesWithNesting = () => {1214const suiteLabel = (label: string) => `SpaceCreatesWithNesting_${label}`;12151216const fakeSpace: Space = {1217key: "fake-space-key",1218id: "fake-space-id",1219homepage: buildFakeContent(),1220};12211222const fakeFile: SiteFileMetadata = {1223fileName: "fake-file-name",1224title: "fake-title",1225originalTitle: "fake-title-original",1226contentBody: {1227storage: {1228value: "fake-value",1229representation: "storage",1230},1231},1232};12331234const fakeNestedFile: SiteFileMetadata = {1235fileName: "fake-parent/fake-file-name",1236title: "fake-title",1237originalTitle: "fake-title-original",12381239contentBody: {1240storage: {1241value: "fake-value",1242representation: "storage",1243},1244},1245};12461247const fakeNestedFileWin: SiteFileMetadata = {1248fileName: "fake-parent\\fake-file-name",1249title: "fake-title",1250originalTitle: "fake-title-original",12511252contentBody: {1253storage: {1254value: "fake-value",1255representation: "storage",1256},1257},1258};12591260const fakeNestedFile2: SiteFileMetadata = {1261fileName: "fake-parent/fake-file-name2",1262title: "fake-title2",1263originalTitle: "fake-title2-original",12641265contentBody: {1266storage: {1267value: "fake-value",1268representation: "storage",1269},1270},1271};12721273const fakeNestedFile2Win: SiteFileMetadata = {1274fileName: "fake-parent\\fake-file-name2",1275title: "fake-title2",1276originalTitle: "fake-title2-original",12771278contentBody: {1279storage: {1280value: "fake-value",1281representation: "storage",1282},1283},1284};12851286const fakeNestedFile3: SiteFileMetadata = {1287fileName: "fake-parent2/fake-file-name3",1288title: "fake-title3",1289originalTitle: "fake-title3-original",12901291contentBody: {1292storage: {1293value: "fake-value",1294representation: "storage",1295},1296},1297};12981299const fakeNestedFile3Win: SiteFileMetadata = {1300fileName: "fake-parent2\\fake-file-name3",1301title: "fake-title3",1302originalTitle: "fake-title3-original",13031304contentBody: {1305storage: {1306value: "fake-value",1307representation: "storage",1308},1309},1310};13111312const fakeMultiNestedFile: SiteFileMetadata = {1313fileName:1314"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name",1315title: "fake-title",1316originalTitle: "fake-title-original",13171318contentBody: {1319storage: {1320value: "fake-value",1321representation: "storage",1322},1323},1324};13251326const fakeMultiNestedFileWin: SiteFileMetadata = {1327fileName:1328"fake-great-grand-parent\\fake-grand-parent\\fake-parent\\fake-file-name",1329title: "fake-title",1330originalTitle: "fake-title-original",13311332contentBody: {1333storage: {1334value: "fake-value",1335representation: "storage",1336},1337},1338};13391340test(suiteLabel("one_nested_file"), async () => {1341const fileMetadataList: SiteFileMetadata[] = [fakeNestedFile];1342const expected: ConfluenceSpaceChange[] = [1343{1344contentChangeType: ContentChangeType.create,1345ancestors: [1346{1347id: "8781825",1348},1349],1350body: {1351storage: {1352representation: "storage",1353value: "",1354},1355},1356fileName: "fake-parent",1357space: {1358key: "fake-space-key",1359id: "fake-space-id",1360homepage: buildFakeContent(),1361},1362status: "current",1363title: "Fake-parent",1364type: "page",1365},1366{1367contentChangeType: ContentChangeType.create,1368ancestors: [1369{1370id: "fake-parent",1371},1372],1373body: {1374storage: {1375representation: "storage",1376value: "fake-value",1377},1378},1379fileName: "fake-parent/fake-file-name",1380space: {1381key: "fake-space-key",1382id: "fake-space-id",1383homepage: buildFakeContent(),1384},1385status: "current",1386title: "fake-title",1387type: "page",1388},1389];1390const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1391fileMetadataList,1392FAKE_PARENT,1393fakeSpace1394);1395assertEquals(expected, actual);1396});13971398test(suiteLabel("one_nested_file_win"), async () => {1399const fileMetadataList: SiteFileMetadata[] = [fakeNestedFileWin];1400const expected: ConfluenceSpaceChange[] = [1401{1402contentChangeType: ContentChangeType.create,1403ancestors: [1404{1405id: "8781825",1406},1407],1408body: {1409storage: {1410representation: "storage",1411value: "",1412},1413},1414fileName: "fake-parent",1415space: {1416key: "fake-space-key",1417id: "fake-space-id",1418homepage: buildFakeContent(),1419},1420status: "current",1421title: "Fake-parent",1422type: "page",1423},1424{1425contentChangeType: ContentChangeType.create,1426ancestors: [1427{1428id: "fake-parent",1429},1430],1431body: {1432storage: {1433representation: "storage",1434value: "fake-value",1435},1436},1437fileName: "fake-parent/fake-file-name",1438space: {1439key: "fake-space-key",1440id: "fake-space-id",1441homepage: buildFakeContent(),1442},1443status: "current",1444title: "fake-title",1445type: "page",1446},1447];1448const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1449fileMetadataList,1450FAKE_PARENT,1451fakeSpace1452);1453assertEquals(expected, actual);1454});14551456test(suiteLabel("one_nested_file_add_back_empty_parent"), async () => {1457const fileMetadataList: SiteFileMetadata[] = [fakeNestedFile];1458const existingSite: SitePage[] = [1459{1460id: "fake-parent-id",1461title: "Fake-parent",1462metadata: { fileName: "fake-parent" },1463},1464];1465const expected: ConfluenceSpaceChange[] = [1466{1467contentChangeType: ContentChangeType.create,1468ancestors: [1469{1470id: "fake-parent-id",1471},1472],1473body: {1474storage: {1475representation: "storage",1476value: "fake-value",1477},1478},1479fileName: "fake-parent/fake-file-name",1480space: {1481key: "fake-space-key",1482id: "fake-space-id",1483homepage: buildFakeContent(),1484},1485status: "current",1486title: "fake-title",1487type: "page",1488},1489];1490const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1491fileMetadataList,1492FAKE_PARENT,1493fakeSpace,1494existingSite1495);1496assertEquals(expected, actual);1497});14981499test(suiteLabel("one_multi_nested_file"), async () => {1500const fileMetadataList: SiteFileMetadata[] = [fakeMultiNestedFile];1501const expected: ConfluenceSpaceChange[] = [1502{1503contentChangeType: ContentChangeType.create,1504ancestors: [1505{1506id: "8781825",1507},1508],1509body: {1510storage: {1511representation: "storage",1512value: "",1513},1514},1515fileName: "fake-great-grand-parent",1516space: {1517key: "fake-space-key",1518id: "fake-space-id",1519homepage: buildFakeContent(),1520},1521status: "current",1522title: "Fake-great-grand-parent",1523type: "page",1524},1525{1526contentChangeType: ContentChangeType.create,1527ancestors: [1528{1529id: "fake-great-grand-parent",1530},1531],1532body: {1533storage: {1534representation: "storage",1535value: "",1536},1537},1538fileName: "fake-great-grand-parent/fake-grand-parent",1539space: {1540key: "fake-space-key",1541id: "fake-space-id",1542homepage: buildFakeContent(),1543},1544status: "current",1545title: "Fake-grand-parent",1546type: "page",1547},1548{1549contentChangeType: ContentChangeType.create,1550ancestors: [1551{1552id: "fake-great-grand-parent/fake-grand-parent",1553},1554],1555body: {1556storage: {1557representation: "storage",1558value: "",1559},1560},1561fileName: "fake-great-grand-parent/fake-grand-parent/fake-parent",1562space: {1563key: "fake-space-key",1564id: "fake-space-id",1565homepage: buildFakeContent(),1566},1567status: "current",1568title: "Fake-parent",1569type: "page",1570},1571{1572contentChangeType: ContentChangeType.create,1573ancestors: [1574{1575id: "fake-great-grand-parent/fake-grand-parent/fake-parent",1576},1577],1578body: {1579storage: {1580representation: "storage",1581value: "fake-value",1582},1583},1584fileName:1585"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name",1586space: {1587key: "fake-space-key",1588id: "fake-space-id",1589homepage: buildFakeContent(),1590},1591status: "current",1592title: "fake-title",1593type: "page",1594},1595];1596const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1597fileMetadataList,1598FAKE_PARENT,1599fakeSpace1600);16011602assertEquals(expected, actual);1603});16041605test(suiteLabel("one_multi_nested_file_win"), async () => {1606const fileMetadataList: SiteFileMetadata[] = [fakeMultiNestedFileWin];1607const expected: ConfluenceSpaceChange[] = [1608{1609contentChangeType: ContentChangeType.create,1610ancestors: [1611{1612id: "8781825",1613},1614],1615body: {1616storage: {1617representation: "storage",1618value: "",1619},1620},1621fileName: "fake-great-grand-parent",1622space: {1623key: "fake-space-key",1624id: "fake-space-id",1625homepage: buildFakeContent(),1626},1627status: "current",1628title: "Fake-great-grand-parent",1629type: "page",1630},1631{1632contentChangeType: ContentChangeType.create,1633ancestors: [1634{1635id: "fake-great-grand-parent",1636},1637],1638body: {1639storage: {1640representation: "storage",1641value: "",1642},1643},1644fileName: "fake-great-grand-parent/fake-grand-parent",1645space: {1646key: "fake-space-key",1647id: "fake-space-id",1648homepage: buildFakeContent(),1649},1650status: "current",1651title: "Fake-grand-parent",1652type: "page",1653},1654{1655contentChangeType: ContentChangeType.create,1656ancestors: [1657{1658id: "fake-great-grand-parent/fake-grand-parent",1659},1660],1661body: {1662storage: {1663representation: "storage",1664value: "",1665},1666},1667fileName: "fake-great-grand-parent/fake-grand-parent/fake-parent",1668space: {1669key: "fake-space-key",1670id: "fake-space-id",1671homepage: buildFakeContent(),1672},1673status: "current",1674title: "Fake-parent",1675type: "page",1676},1677{1678contentChangeType: ContentChangeType.create,1679ancestors: [1680{1681id: "fake-great-grand-parent/fake-grand-parent/fake-parent",1682},1683],1684body: {1685storage: {1686representation: "storage",1687value: "fake-value",1688},1689},1690fileName:1691"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name",1692space: {1693key: "fake-space-key",1694id: "fake-space-id",1695homepage: buildFakeContent(),1696},1697status: "current",1698title: "fake-title",1699type: "page",1700},1701];1702const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1703fileMetadataList,1704FAKE_PARENT,1705fakeSpace1706);17071708assertEquals(expected, actual);1709});17101711test(suiteLabel("two_nested_files_same_parent"), async () => {1712const fileMetadataList: SiteFileMetadata[] = [1713fakeNestedFile,1714fakeNestedFile2,1715];1716const expected: ConfluenceSpaceChange[] = [1717{1718contentChangeType: ContentChangeType.create,1719ancestors: [1720{1721id: "8781825",1722},1723],1724body: {1725storage: {1726representation: "storage",1727value: "",1728},1729},1730fileName: "fake-parent",1731space: {1732key: "fake-space-key",1733id: "fake-space-id",1734homepage: buildFakeContent(),1735},1736status: "current",1737title: "Fake-parent",1738type: "page",1739},1740{1741contentChangeType: ContentChangeType.create,1742ancestors: [1743{1744id: "fake-parent",1745},1746],1747body: {1748storage: {1749representation: "storage",1750value: "fake-value",1751},1752},1753fileName: "fake-parent/fake-file-name",1754space: {1755key: "fake-space-key",1756id: "fake-space-id",1757homepage: buildFakeContent(),1758},1759status: "current",1760title: "fake-title",1761type: "page",1762},1763{1764contentChangeType: ContentChangeType.create,1765ancestors: [1766{1767id: "fake-parent",1768},1769],1770body: {1771storage: {1772representation: "storage",1773value: "fake-value",1774},1775},1776fileName: "fake-parent/fake-file-name2",1777space: {1778key: "fake-space-key",1779id: "fake-space-id",1780homepage: buildFakeContent(),1781},1782status: "current",1783title: "fake-title2",1784type: "page",1785},1786];1787const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1788fileMetadataList,1789FAKE_PARENT,1790fakeSpace1791);1792assertEquals(expected, actual);1793});17941795test(suiteLabel("two_nested_files_different_parent"), async () => {1796const fileMetadataList: SiteFileMetadata[] = [1797fakeNestedFile,1798fakeNestedFile3,1799];1800const expected: ConfluenceSpaceChange[] = [1801{1802contentChangeType: ContentChangeType.create,1803ancestors: [1804{1805id: "8781825",1806},1807],1808body: {1809storage: {1810representation: "storage",1811value: "",1812},1813},1814fileName: "fake-parent",1815space: {1816key: "fake-space-key",1817id: "fake-space-id",1818homepage: buildFakeContent(),1819},1820status: "current",1821title: "Fake-parent",1822type: "page",1823},1824{1825contentChangeType: ContentChangeType.create,1826ancestors: [1827{1828id: "fake-parent",1829},1830],1831body: {1832storage: {1833representation: "storage",1834value: "fake-value",1835},1836},1837fileName: "fake-parent/fake-file-name",1838space: {1839key: "fake-space-key",1840id: "fake-space-id",1841homepage: buildFakeContent(),1842},1843status: "current",1844title: "fake-title",1845type: "page",1846},1847{1848contentChangeType: ContentChangeType.create,1849ancestors: [1850{1851id: "8781825",1852},1853],1854body: {1855storage: {1856representation: "storage",1857value: "",1858},1859},1860fileName: "fake-parent2",1861space: {1862key: "fake-space-key",1863id: "fake-space-id",1864homepage: buildFakeContent(),1865},1866status: "current",1867title: "Fake-parent2",1868type: "page",1869},1870{1871contentChangeType: ContentChangeType.create,1872ancestors: [1873{1874id: "fake-parent2",1875},1876],1877body: {1878storage: {1879representation: "storage",1880value: "fake-value",1881},1882},1883fileName: "fake-parent2/fake-file-name3",1884space: {1885key: "fake-space-key",1886id: "fake-space-id",1887homepage: buildFakeContent(),1888},1889status: "current",1890title: "fake-title3",1891type: "page",1892},1893];1894const actual: ConfluenceSpaceChange[] = buildSpaceChanges(1895fileMetadataList,1896FAKE_PARENT,1897fakeSpace1898);1899assertEquals(expected, actual);1900});19011902test(suiteLabel("two_nested_files_different_parent_win"), async () => {1903const fileMetadataList: SiteFileMetadata[] = [1904fakeNestedFileWin,1905fakeNestedFile3Win,1906];1907const expected: ConfluenceSpaceChange[] = [1908{1909contentChangeType: ContentChangeType.create,1910ancestors: [1911{1912id: "8781825",1913},1914],1915body: {1916storage: {1917representation: "storage",1918value: "",1919},1920},1921fileName: "fake-parent",1922space: {1923key: "fake-space-key",1924id: "fake-space-id",1925homepage: buildFakeContent(),1926},1927status: "current",1928title: "Fake-parent",1929type: "page",1930},1931{1932contentChangeType: ContentChangeType.create,1933ancestors: [1934{1935id: "fake-parent",1936},1937],1938body: {1939storage: {1940representation: "storage",1941value: "fake-value",1942},1943},1944fileName: "fake-parent/fake-file-name",1945space: {1946key: "fake-space-key",1947id: "fake-space-id",1948homepage: buildFakeContent(),1949},1950status: "current",1951title: "fake-title",1952type: "page",1953},1954{1955contentChangeType: ContentChangeType.create,1956ancestors: [1957{1958id: "8781825",1959},1960],1961body: {1962storage: {1963representation: "storage",1964value: "",1965},1966},1967fileName: "fake-parent2",1968space: {1969key: "fake-space-key",1970id: "fake-space-id",1971homepage: buildFakeContent(),1972},1973status: "current",1974title: "Fake-parent2",1975type: "page",1976},1977{1978contentChangeType: ContentChangeType.create,1979ancestors: [1980{1981id: "fake-parent2",1982},1983],1984body: {1985storage: {1986representation: "storage",1987value: "fake-value",1988},1989},1990fileName: "fake-parent2/fake-file-name3",1991space: {1992key: "fake-space-key",1993id: "fake-space-id",1994homepage: buildFakeContent(),1995},1996status: "current",1997title: "fake-title3",1998type: "page",1999},2000];2001const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2002fileMetadataList,2003FAKE_PARENT,2004fakeSpace2005);2006assertEquals(expected, actual);2007});20082009test(suiteLabel("three_nested_files_same_different_parent"), async () => {2010const fileMetadataList: SiteFileMetadata[] = [2011fakeNestedFile,2012fakeNestedFile2,2013fakeNestedFile3,2014];2015const expected: ConfluenceSpaceChange[] = [2016{2017contentChangeType: ContentChangeType.create,2018ancestors: [2019{2020id: "8781825",2021},2022],2023body: {2024storage: {2025representation: "storage",2026value: "",2027},2028},2029fileName: "fake-parent",2030space: {2031key: "fake-space-key",2032id: "fake-space-id",2033homepage: buildFakeContent(),2034},2035status: "current",2036title: "Fake-parent",2037type: "page",2038},2039{2040contentChangeType: ContentChangeType.create,2041ancestors: [2042{2043id: "fake-parent",2044},2045],2046body: {2047storage: {2048representation: "storage",2049value: "fake-value",2050},2051},2052fileName: "fake-parent/fake-file-name",2053space: {2054key: "fake-space-key",2055id: "fake-space-id",2056homepage: buildFakeContent(),2057},2058status: "current",2059title: "fake-title",2060type: "page",2061},2062{2063contentChangeType: ContentChangeType.create,2064ancestors: [2065{2066id: "fake-parent",2067},2068],2069body: {2070storage: {2071representation: "storage",2072value: "fake-value",2073},2074},2075fileName: "fake-parent/fake-file-name2",2076space: {2077key: "fake-space-key",2078id: "fake-space-id",2079homepage: buildFakeContent(),2080},2081status: "current",2082title: "fake-title2",2083type: "page",2084},2085{2086contentChangeType: ContentChangeType.create,2087ancestors: [2088{2089id: "8781825",2090},2091],2092body: {2093storage: {2094representation: "storage",2095value: "",2096},2097},2098fileName: "fake-parent2",2099space: {2100key: "fake-space-key",2101id: "fake-space-id",2102homepage: buildFakeContent(),2103},2104status: "current",2105title: "Fake-parent2",2106type: "page",2107},2108{2109contentChangeType: ContentChangeType.create,2110ancestors: [2111{2112id: "fake-parent2",2113},2114],2115body: {2116storage: {2117representation: "storage",2118value: "fake-value",2119},2120},2121fileName: "fake-parent2/fake-file-name3",2122space: {2123key: "fake-space-key",2124id: "fake-space-id",2125homepage: buildFakeContent(),2126},2127status: "current",2128title: "fake-title3",2129type: "page",2130},2131];2132const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2133fileMetadataList,2134FAKE_PARENT,2135fakeSpace2136);2137assertEquals(expected, actual);2138});21392140test(suiteLabel("three_nested_files_same_different_parent_win"), async () => {2141const fileMetadataList: SiteFileMetadata[] = [2142fakeNestedFileWin,2143fakeNestedFile2Win,2144fakeNestedFile3Win,2145];2146const expected: ConfluenceSpaceChange[] = [2147{2148contentChangeType: ContentChangeType.create,2149ancestors: [2150{2151id: "8781825",2152},2153],2154body: {2155storage: {2156representation: "storage",2157value: "",2158},2159},2160fileName: "fake-parent",2161space: {2162key: "fake-space-key",2163id: "fake-space-id",2164homepage: buildFakeContent(),2165},2166status: "current",2167title: "Fake-parent",2168type: "page",2169},2170{2171contentChangeType: ContentChangeType.create,2172ancestors: [2173{2174id: "fake-parent",2175},2176],2177body: {2178storage: {2179representation: "storage",2180value: "fake-value",2181},2182},2183fileName: "fake-parent/fake-file-name",2184space: {2185key: "fake-space-key",2186id: "fake-space-id",2187homepage: buildFakeContent(),2188},2189status: "current",2190title: "fake-title",2191type: "page",2192},2193{2194contentChangeType: ContentChangeType.create,2195ancestors: [2196{2197id: "fake-parent",2198},2199],2200body: {2201storage: {2202representation: "storage",2203value: "fake-value",2204},2205},2206fileName: "fake-parent/fake-file-name2",2207space: {2208key: "fake-space-key",2209id: "fake-space-id",2210homepage: buildFakeContent(),2211},2212status: "current",2213title: "fake-title2",2214type: "page",2215},2216{2217contentChangeType: ContentChangeType.create,2218ancestors: [2219{2220id: "8781825",2221},2222],2223body: {2224storage: {2225representation: "storage",2226value: "",2227},2228},2229fileName: "fake-parent2",2230space: {2231key: "fake-space-key",2232id: "fake-space-id",2233homepage: buildFakeContent(),2234},2235status: "current",2236title: "Fake-parent2",2237type: "page",2238},2239{2240contentChangeType: ContentChangeType.create,2241ancestors: [2242{2243id: "fake-parent2",2244},2245],2246body: {2247storage: {2248representation: "storage",2249value: "fake-value",2250},2251},2252fileName: "fake-parent2/fake-file-name3",2253space: {2254key: "fake-space-key",2255id: "fake-space-id",2256homepage: buildFakeContent(),2257},2258status: "current",2259title: "fake-title3",2260type: "page",2261},2262];2263const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2264fileMetadataList,2265FAKE_PARENT,2266fakeSpace2267);2268assertEquals(expected, actual);2269});2270};22712272const runSpaceUpdatesWithNesting = () => {2273const suiteLabel = (label: string) => `SpaceUpdatesWithNesting_${label}`;22742275const fakeSpace: Space = {2276key: "fake-space-key",2277id: "fake-space-id",2278homepage: buildFakeContent(),2279};22802281const fakeNestedFile: SiteFileMetadata = {2282fileName: "fake-parent/fake-file-name.xml",2283title: "fake-title",2284originalTitle: "fake-title-original",22852286contentBody: {2287storage: {2288value: "fake-value",2289representation: "storage",2290},2291},2292};22932294const fakeNestedFile2: SiteFileMetadata = {2295fileName: "fake-parent/fake-file-name2.xml",2296title: "fake-title2",2297originalTitle: "fake-title2-original",22982299contentBody: {2300storage: {2301value: "fake-value",2302representation: "storage",2303},2304},2305};23062307const fakeNestedFile3: SiteFileMetadata = {2308fileName: "fake-parent2/fake-file-name3.xml",2309title: "fake-title3",2310originalTitle: "fake-title3-original",23112312contentBody: {2313storage: {2314value: "fake-value",2315representation: "storage",2316},2317},2318};23192320const fakeMultiNestedFile: SiteFileMetadata = {2321fileName:2322"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name.xml",2323title: "fake-title",2324originalTitle: "fake-title-original",23252326contentBody: {2327storage: {2328value: "fake-value",2329representation: "storage",2330},2331},2332};23332334const fakeMultiNestedFileWin: SiteFileMetadata = {2335fileName:2336"fake-great-grand-parent\\fake-grand-parent\\fake-parent\\fake-file-name.xml",2337title: "fake-title",2338originalTitle: "fake-title-original",23392340contentBody: {2341storage: {2342value: "fake-value",2343representation: "storage",2344},2345},2346};23472348test(suiteLabel("one_nested_file_update"), async () => {2349const fileMetadataList: SiteFileMetadata[] = [fakeNestedFile];23502351const existingSite = [2352{2353id: "123456",2354title: "fake-title",2355metadata: { fileName: "fake-parent/fake-file-name.xml" },2356ancestors: [{ id: "fake-parent-id" }, { id: "fake-grand-parent-id" }],2357},2358{2359title: "Fake Parent",2360id: "fake-parent-id",2361metadata: { editor: "v2", fileName: "fake-parent" },2362ancestors: [{ id: "fake-grand-parent-id" }],2363},2364];23652366const expected: ConfluenceSpaceChange[] = [2367{2368contentChangeType: ContentChangeType.update,2369ancestors: [2370{2371id: "fake-parent-id",2372},2373],2374body: {2375storage: {2376representation: "storage",2377value: "fake-value",2378},2379},2380fileName: "fake-parent/fake-file-name.xml",2381status: "current",2382title: "fake-title",2383type: "page",2384id: "123456",2385version: null,2386},2387];23882389assertEquals(findPagesToDelete(fileMetadataList, existingSite), []);23902391const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2392fileMetadataList,2393FAKE_PARENT,2394fakeSpace,2395existingSite2396);23972398assertEquals(expected, actual);2399});24002401test(suiteLabel("one_multi-nested_file_update"), async () => {2402const fileMetadataList: SiteFileMetadata[] = [fakeMultiNestedFile];24032404const existingSite = [2405{2406id: "123456",2407title: "fake-title",2408metadata: {2409fileName:2410"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name.xml",2411},2412ancestors: [2413{ id: "fake-parent-id" },2414{ id: "fake-grand-parent-id" },2415{ id: "fake-great-grand-parent-id" },2416],2417},2418{2419title: "Fake Parent",2420id: "fake-parent-id",2421metadata: {2422editor: "v2",2423fileName: "fake-great-grand-parent/fake-grand-parent/fake-parent",2424},2425ancestors: [2426{ id: "fake-grand-parent-id" },2427{ id: "fake-great-grand-parent-id" },2428],2429},2430{2431title: "Fake Grand Parent",2432id: "fake-grand-parent-id",2433metadata: {2434editor: "v2",2435fileName: "fake-great-grand-parent/fake-grand-parent",2436},2437ancestors: [{ id: "fake-great-grand-parent-id" }],2438},2439{2440title: "Fake Great Grand Parent",2441id: "fake-great-grand-parent-id",2442metadata: { editor: "v2", fileName: "fake-great-grand-parent" },2443ancestors: [{ id: "fake-great-grand-parent-id" }],2444},2445];24462447const expected: ConfluenceSpaceChange[] = [2448{2449contentChangeType: ContentChangeType.update,2450ancestors: [2451{2452id: "fake-parent-id",2453},2454],2455body: {2456storage: {2457representation: "storage",2458value: "fake-value",2459},2460},2461fileName:2462"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name.xml",2463status: "current",2464title: "fake-title",2465type: "page",2466id: "123456",2467version: null,2468},2469];24702471const pagesToDelete = findPagesToDelete(fileMetadataList, existingSite);24722473assertEquals(pagesToDelete, []);24742475const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2476fileMetadataList,2477FAKE_PARENT,2478fakeSpace,2479existingSite2480);24812482assertEquals(expected, actual);2483});24842485test(suiteLabel("one_multi-nested_file_update_win"), async () => {2486const fileMetadataList: SiteFileMetadata[] = [fakeMultiNestedFileWin];24872488const existingSite = [2489{2490id: "123456",2491title: "fake-title",2492metadata: {2493fileName:2494"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name.xml",2495},2496ancestors: [2497{ id: "fake-parent-id" },2498{ id: "fake-grand-parent-id" },2499{ id: "fake-great-grand-parent-id" },2500],2501},2502{2503title: "Fake Parent",2504id: "fake-parent-id",2505metadata: {2506editor: "v2",2507fileName: "fake-great-grand-parent/fake-grand-parent/fake-parent",2508},2509ancestors: [2510{ id: "fake-grand-parent-id" },2511{ id: "fake-great-grand-parent-id" },2512],2513},2514{2515title: "Fake Grand Parent",2516id: "fake-grand-parent-id",2517metadata: {2518editor: "v2",2519fileName: "fake-great-grand-parent/fake-grand-parent",2520},2521ancestors: [{ id: "fake-great-grand-parent-id" }],2522},2523{2524title: "Fake Great Grand Parent",2525id: "fake-great-grand-parent-id",2526metadata: { editor: "v2", fileName: "fake-great-grand-parent" },2527ancestors: [{ id: "fake-great-grand-parent-id" }],2528},2529];25302531const expected: ConfluenceSpaceChange[] = [2532{2533contentChangeType: ContentChangeType.update,2534ancestors: [2535{2536id: "fake-parent-id",2537},2538],2539body: {2540storage: {2541representation: "storage",2542value: "fake-value",2543},2544},2545fileName:2546"fake-great-grand-parent/fake-grand-parent/fake-parent/fake-file-name.xml",2547status: "current",2548title: "fake-title",2549type: "page",2550id: "123456",2551version: null,2552},2553];25542555const pagesToDelete = findPagesToDelete(fileMetadataList, existingSite);25562557assertEquals(pagesToDelete, []);25582559const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2560fileMetadataList,2561FAKE_PARENT,2562fakeSpace,2563existingSite2564);25652566assertEquals(expected, actual);2567});25682569test(suiteLabel("two_nested_files_same_parent"), async () => {2570const fileMetadataList: SiteFileMetadata[] = [2571fakeNestedFile,2572fakeNestedFile2,2573];2574const existingSite = [2575{2576id: "fake-title-id",2577title: "fake-title",2578metadata: { fileName: "fake-parent/fake-file-name.xml" },2579ancestors: [{ id: "fake-parent-id" }, { id: "fake-grand-parent-id" }],2580},2581{2582id: "fake-title2-id",2583title: "fake-title2",2584metadata: { fileName: "fake-parent/fake-file-name2.xml" },2585ancestors: [{ id: "fake-parent-id" }, { id: "fake-grand-parent-id" }],2586},2587{2588title: "Fake Parent",2589id: "fake-parent-id",2590metadata: { editor: "v2", fileName: "fake-parent" },2591ancestors: [{ id: "fake-grand-parent-id" }],2592},2593];25942595const expected: ConfluenceSpaceChange[] = [2596{2597contentChangeType: ContentChangeType.update,2598ancestors: [2599{2600id: "fake-parent-id",2601},2602],2603body: {2604storage: {2605representation: "storage",2606value: "fake-value",2607},2608},2609fileName: "fake-parent/fake-file-name.xml",2610status: "current",2611title: "fake-title",2612type: "page",2613id: "fake-title-id",2614version: null,2615},2616{2617contentChangeType: ContentChangeType.update,2618ancestors: [2619{2620id: "fake-parent-id",2621},2622],2623body: {2624storage: {2625representation: "storage",2626value: "fake-value",2627},2628},2629fileName: "fake-parent/fake-file-name2.xml",2630status: "current",2631title: "fake-title2",2632type: "page",2633id: "fake-title2-id",2634version: null,2635},2636];2637const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2638fileMetadataList,2639FAKE_PARENT,2640fakeSpace,2641existingSite2642);2643assertEquals(expected, actual);2644});26452646test(suiteLabel("three_nested_files_same_different_parent"), async () => {2647const fileMetadataList: SiteFileMetadata[] = [2648fakeNestedFile,2649fakeNestedFile2,2650fakeNestedFile3,2651];26522653const existingSite = [2654{2655id: "fake-title-id",2656title: "fake-title",2657metadata: { fileName: "fake-parent/fake-file-name.xml" },2658ancestors: [{ id: "fake-parent-id" }, { id: "fake-grand-parent-id" }],2659},2660{2661id: "fake-title2-id",2662title: "fake-title2",2663metadata: { fileName: "fake-parent/fake-file-name2.xml" },2664ancestors: [{ id: "fake-parent-id" }, { id: "fake-grand-parent-id" }],2665},2666{2667id: "fake-title3-id",2668title: "fake-title3",2669metadata: { fileName: "fake-parent2/fake-file-name3.xml" },2670ancestors: [{ id: "fake-parent2-id" }, { id: "fake-grand-parent-id" }],2671},2672{2673title: "Fake Parent",2674id: "fake-parent-id",2675metadata: { editor: "v2", fileName: "fake-parent" },2676ancestors: [{ id: "fake-grand-parent-id" }],2677},2678{2679title: "Fake Parent2",2680id: "fake-parent2-id",2681metadata: { editor: "v2", fileName: "fake-parent2" },2682ancestors: [{ id: "fake-grand-parent-id" }],2683},2684];26852686const expected: ConfluenceSpaceChange[] = [2687{2688contentChangeType: ContentChangeType.update,2689ancestors: [2690{2691id: "fake-parent-id",2692},2693],2694body: {2695storage: {2696representation: "storage",2697value: "fake-value",2698},2699},2700fileName: "fake-parent/fake-file-name.xml",2701status: "current",2702title: "fake-title",2703type: "page",2704id: "fake-title-id",2705version: null,2706},2707{2708contentChangeType: ContentChangeType.update,2709ancestors: [2710{2711id: "fake-parent-id",2712},2713],2714body: {2715storage: {2716representation: "storage",2717value: "fake-value",2718},2719},2720fileName: "fake-parent/fake-file-name2.xml",2721status: "current",2722title: "fake-title2",2723type: "page",2724id: "fake-title2-id",2725version: null,2726},2727{2728contentChangeType: ContentChangeType.update,2729ancestors: [2730{2731id: "fake-parent2-id",2732},2733],2734body: {2735storage: {2736representation: "storage",2737value: "fake-value",2738},2739},2740fileName: "fake-parent2/fake-file-name3.xml",2741status: "current",2742title: "fake-title3",2743type: "page",2744id: "fake-title3-id",2745version: null,2746},2747];2748const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2749fileMetadataList,2750FAKE_PARENT,2751fakeSpace,2752existingSite2753);2754assertEquals(expected, actual);2755});2756};27572758const runSpaceUpdatesWithNestedMoves = () => {2759const suiteLabel = (label: string) => `SpaceUpdatesWithNesting_${label}`;27602761const fakeSpace: Space = {2762key: "fake-space-key",2763id: "fake-space-id",2764homepage: buildFakeContent(),2765};27662767const fakeNestedFile: SiteFileMetadata = {2768fileName: "fake-parent/fake-file-name.xml",2769title: "fake-title",2770originalTitle: "fake-title-original",2771contentBody: {2772storage: {2773value: "fake-value",2774representation: "storage",2775},2776},2777};27782779const fakeRootFile: SiteFileMetadata = {2780fileName: "fake-file-name.xml",2781title: "fake-title",2782originalTitle: "fake-title-original",2783contentBody: {2784storage: {2785value: "fake-value",2786representation: "storage",2787},2788},2789};27902791const fakeNestedFile2: SiteFileMetadata = {2792fileName: "fake-parent/fake-file-name2.xml",2793title: "fake-title2",2794originalTitle: "fake-title2-original",27952796contentBody: {2797storage: {2798value: "fake-value",2799representation: "storage",2800},2801},2802};28032804const fakeNestedFile3: SiteFileMetadata = {2805fileName: "fake-parent2/fake-file-name3.xml",2806title: "fake-title3",2807originalTitle: "fake-title3-original",28082809contentBody: {2810storage: {2811value: "fake-value",2812representation: "storage",2813},2814},2815};28162817const fakeMultiNestedFile: SiteFileMetadata = {2818fileName: "fake-parent/fake-inner-parent/fake-file-name.xml",2819title: "fake-multi-nested-title",2820originalTitle: "fake-multi-nested-title-original",2821contentBody: {2822storage: {2823value: "fake-value",2824representation: "storage",2825},2826},2827};28282829test(suiteLabel("move_from_root_to_nested_parent"), async () => {2830const fileMetadataList: SiteFileMetadata[] = [fakeNestedFile];2831const existingSite = [2832{2833id: "fake-title-id",2834title: "fake-title",2835metadata: { fileName: "fake-file-name.xml" },2836ancestors: [{ id: "fake-grand-parent-id" }],2837},2838];28392840const expected: ConfluenceSpaceChange[] = [2841{2842contentChangeType: ContentChangeType.delete,2843id: "fake-title-id",2844},2845{2846contentChangeType: ContentChangeType.create,2847ancestors: [2848{2849id: "8781825",2850},2851],2852body: {2853storage: {2854representation: "storage",2855value: "",2856},2857},2858fileName: "fake-parent",2859space: {2860key: "fake-space-key",2861id: "fake-space-id",2862homepage: buildFakeContent(),2863},2864status: "current",2865title: "Fake-parent",2866type: "page",2867},2868{2869contentChangeType: ContentChangeType.create,2870ancestors: [2871{2872id: "fake-parent",2873},2874],2875body: {2876storage: {2877representation: "storage",2878value: "fake-value",2879},2880},2881fileName: "fake-parent/fake-file-name.xml",2882space: {2883key: "fake-space-key",2884id: "fake-space-id",2885homepage: buildFakeContent(),2886},2887status: "current",2888title: "fake-title",2889type: "page",2890},2891];2892const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2893fileMetadataList,2894FAKE_PARENT,2895fakeSpace,2896existingSite2897);2898assertEquals(expected, actual);2899});29002901test(suiteLabel("add_file_existing_parent"), async () => {2902const fileMetadataList: SiteFileMetadata[] = [2903fakeNestedFile,2904fakeNestedFile2,2905];2906const existingSite = [2907{2908id: "fake-title-id",2909title: "fake-title",2910metadata: { fileName: "fake-parent/fake-file-name.xml" },2911ancestors: [{ id: "fake-grand-parent-id" }, { id: "fake-parent-id" }],2912},2913{2914title: "Fake Parent",2915id: "fake-parent-id",2916metadata: { editor: "v2", fileName: "fake-parent" },2917ancestors: [{ id: "fake-grand-parent-id" }],2918},2919];29202921const expected: ConfluenceSpaceChange[] = [2922{2923contentChangeType: ContentChangeType.update,2924id: "fake-title-id",2925version: null,2926title: "fake-title",2927type: "page",2928status: "current",2929ancestors: [{ id: "fake-parent-id" }],2930body: { storage: { value: "fake-value", representation: "storage" } },2931fileName: "fake-parent/fake-file-name.xml",2932},2933{2934contentChangeType: ContentChangeType.create,2935ancestors: [2936{2937id: "fake-parent-id",2938},2939],2940body: {2941storage: {2942representation: "storage",2943value: "fake-value",2944},2945},2946fileName: "fake-parent/fake-file-name2.xml",2947space: {2948key: "fake-space-key",2949id: "fake-space-id",2950homepage: buildFakeContent(),2951},2952status: "current",2953title: "fake-title2",2954type: "page",2955},2956];2957const actual: ConfluenceSpaceChange[] = buildSpaceChanges(2958fileMetadataList,2959FAKE_PARENT,2960fakeSpace,2961existingSite2962);2963assertEquals(expected, actual);2964});29652966test(suiteLabel("from_root_to_existing_parent"), async () => {2967const fileMetadataList: SiteFileMetadata[] = [2968fakeNestedFile,2969fakeNestedFile2,2970];2971const existingSite = [2972{2973id: "fake-title-id",2974title: "fake-title",2975metadata: { fileName: "fake-file-name.xml" },2976ancestors: [{ id: "fake-grand-parent-id" }],2977},2978{2979id: "fake-title2-id",2980title: "fake-title",2981metadata: { fileName: "fake-parent/fake-file-name2.xml" },2982ancestors: [{ id: "fake-grand-parent-id" }, { id: "fake-parent-id" }],2983},2984{2985title: "Fake Parent",2986id: "fake-parent-id",2987metadata: { editor: "v2", fileName: "fake-parent" },2988ancestors: [{ id: "fake-grand-parent-id" }],2989},2990];29912992const expected: ConfluenceSpaceChange[] = [2993{ contentChangeType: ContentChangeType.delete, id: "fake-title-id" },2994{2995contentChangeType: ContentChangeType.create,2996ancestors: [2997{2998id: "fake-parent-id",2999},3000],3001body: {3002storage: {3003representation: "storage",3004value: "fake-value",3005},3006},3007fileName: "fake-parent/fake-file-name.xml",3008space: {3009key: "fake-space-key",3010id: "fake-space-id",3011homepage: buildFakeContent(),3012},3013status: "current",3014title: "fake-title",3015type: "page",3016},3017{3018contentChangeType: ContentChangeType.update,3019id: "fake-title2-id",3020version: null,3021title: "fake-title2",3022type: "page",3023status: "current",3024ancestors: [{ id: "fake-parent-id" }],3025body: { storage: { value: "fake-value", representation: "storage" } },3026fileName: "fake-parent/fake-file-name2.xml",3027},3028];3029const actual: ConfluenceSpaceChange[] = buildSpaceChanges(3030fileMetadataList,3031FAKE_PARENT,3032fakeSpace,3033existingSite3034);3035assertEquals(expected, actual);3036});30373038test(suiteLabel("create_in_nested_parent"), async () => {3039const fileMetadataList: SiteFileMetadata[] = [3040fakeNestedFile,3041fakeMultiNestedFile,3042];3043const existingSite = [3044{3045id: "fake-title-id",3046title: "fake-title",3047metadata: { fileName: "fake-parent/fake-file-name.xml" },3048ancestors: [{ id: "fake-grand-parent-id" }, { id: "fake-parent-id" }],3049},3050{3051title: "Fake Parent",3052id: "fake-parent-id",3053metadata: { editor: "v2", fileName: "fake-parent" },3054ancestors: [{ id: "fake-grand-parent-id" }],3055},3056];30573058const expected: ConfluenceSpaceChange[] = [3059{3060contentChangeType: ContentChangeType.update,3061id: "fake-title-id",3062version: null,3063title: "fake-title",3064type: "page",3065status: "current",3066ancestors: [{ id: "fake-parent-id" }],3067body: { storage: { value: "fake-value", representation: "storage" } },3068fileName: "fake-parent/fake-file-name.xml",3069},3070{3071contentChangeType: ContentChangeType.create,3072title: "Fake-inner-parent",3073type: "page",3074ancestors: [3075{3076id: "fake-parent-id",3077},3078],3079body: {3080storage: {3081representation: "storage",3082value: "",3083},3084},3085fileName: "fake-parent/fake-inner-parent",3086space: {3087key: "fake-space-key",3088id: "fake-space-id",3089homepage: buildFakeContent(),3090},3091status: "current",3092},3093{3094contentChangeType: ContentChangeType.create,3095title: "fake-multi-nested-title",3096type: "page",3097ancestors: [3098{3099id: "fake-parent/fake-inner-parent",3100},3101],3102body: {3103storage: {3104representation: "storage",3105value: "fake-value",3106},3107},3108fileName: "fake-parent/fake-inner-parent/fake-file-name.xml",3109space: {3110key: "fake-space-key",3111id: "fake-space-id",3112homepage: buildFakeContent(),3113},3114status: "current",3115},3116];3117const actual: ConfluenceSpaceChange[] = buildSpaceChanges(3118fileMetadataList,3119FAKE_PARENT,3120fakeSpace,3121existingSite3122);3123assertEquals(expected, actual);3124});3125};31263127const runFlattenIndexes = () => {3128const suiteLabel = (label: string) => `FlattenIndexes_${label}`;31293130const FAKE_SITE_PARENT_ID = "fake-site-parent-id";31313132const FAKE_METADATA_ONE_FOLDER = {3133["fake-parent"]: {3134title: "Fake Parent Title",3135id: "fake-parent-id",3136metadata: {3137fileName: "fake-parent",3138},3139ancestors: [3140{3141id: "fake-space-id",3142},3143],3144},3145};31463147const FAKE_METADATA_EMPTY = {};31483149test(suiteLabel("no_files"), async () => {3150const spaceChanges: ConfluenceSpaceChange[] = [];3151const expected: ConfluenceSpaceChange[] = [];3152const actual: ConfluenceSpaceChange[] = flattenIndexes(3153spaceChanges,3154FAKE_METADATA_EMPTY,3155FAKE_SITE_PARENT_ID3156);3157assertEquals(expected, actual);3158});31593160test(suiteLabel("no_folders"), async () => {3161const spaceChanges: ConfluenceSpaceChange[] = [3162{3163contentChangeType: ContentChangeType.create,3164ancestors: [3165{3166id: "8781825",3167},3168],3169body: {3170storage: {3171representation: "storage",3172value: "fake-value",3173},3174},3175fileName: "fake-file-name",3176space: {3177key: "fake-space-key",3178id: "fake-space-id",3179homepage: buildFakeContent(),3180},3181status: "current",3182title: "fake-title",3183type: "page",3184},3185];3186const expected: ConfluenceSpaceChange[] = spaceChanges;3187const actual: ConfluenceSpaceChange[] = flattenIndexes(3188spaceChanges,3189FAKE_METADATA_EMPTY,3190FAKE_SITE_PARENT_ID3191);3192assertEquals(expected, actual);3193});31943195test(suiteLabel("one_folder_no_index"), async () => {3196const spaceChanges: ConfluenceSpaceChange[] = [3197{3198contentChangeType: ContentChangeType.create,3199ancestors: [3200{3201id: "8781825",3202},3203],3204body: {3205storage: {3206representation: "storage",3207value: "",3208},3209},3210fileName: "fake-parent",3211space: {3212key: "fake-space-key",3213id: "fake-space-id",3214homepage: buildFakeContent(),3215},3216status: "current",3217title: "Fake-parent",3218type: "page",3219},3220{3221contentChangeType: ContentChangeType.create,3222ancestors: [3223{3224id: "fake-parent",3225},3226],3227body: {3228storage: {3229representation: "storage",3230value: "fake-value",3231},3232},3233fileName: "fake-parent/fake-file-name",3234space: {3235key: "fake-space-key",3236id: "fake-space-id",3237homepage: buildFakeContent(),3238},3239status: "current",3240title: "fake-title",3241type: "page",3242},3243];3244const expected: ConfluenceSpaceChange[] = spaceChanges;3245const actual: ConfluenceSpaceChange[] = flattenIndexes(3246spaceChanges,3247FAKE_METADATA_EMPTY,3248FAKE_SITE_PARENT_ID3249);3250assertEquals(expected, actual);3251});32523253test(suiteLabel("create_one_folder_with_index"), async () => {3254const spaceChanges: ConfluenceSpaceChange[] = [3255{3256contentChangeType: ContentChangeType.create,3257ancestors: [3258{3259id: "8781825",3260},3261],3262body: {3263storage: {3264representation: "storage",3265value: "",3266},3267},3268fileName: "fake-parent",3269space: {3270key: "fake-space-key",3271id: "fake-space-id",3272homepage: buildFakeContent(),3273},3274status: "current",3275title: "Fake-parent",3276type: "page",3277},3278{3279contentChangeType: ContentChangeType.create,3280ancestors: [3281{3282id: "fake-parent",3283},3284],3285body: {3286storage: {3287representation: "storage",3288value: "fake-index-value",3289},3290},3291fileName: "fake-parent/index.xml",3292space: {3293key: "fake-space-key",3294id: "fake-space-id",3295homepage: buildFakeContent(),3296},3297status: "current",3298title: "fake-index-title",3299type: "page",3300},3301];3302const expected: ConfluenceSpaceChange[] = [3303{3304contentChangeType: ContentChangeType.create,3305ancestors: [3306{3307id: "8781825",3308},3309],3310body: {3311storage: {3312representation: "storage",3313value: "fake-index-value",3314},3315},3316fileName: "fake-parent",3317space: {3318key: "fake-space-key",3319id: "fake-space-id",3320homepage: buildFakeContent(),3321},3322status: "current",3323title: "fake-index-title",3324type: "page",3325},3326];3327const actual: ConfluenceSpaceChange[] = flattenIndexes(3328spaceChanges,3329FAKE_METADATA_EMPTY,3330FAKE_SITE_PARENT_ID3331);3332assertEquals(expected, actual);3333});33343335test(suiteLabel("create_root_with_index"), async () => {3336const spaceChanges: ConfluenceSpaceChange[] = [3337{3338contentChangeType: ContentChangeType.create,3339ancestors: [3340{3341id: "8781825",3342},3343],3344body: {3345storage: {3346representation: "storage",3347value: "fake content root index",3348},3349},3350fileName: "index.xml",3351space: {3352key: "fake-space-key",3353id: "fake-space-id",3354homepage: buildFakeContent(),3355},3356status: "current",3357title: "Root Index",3358type: "page",3359},3360];3361const expected: ConfluenceSpaceChange[] = [3362{3363contentChangeType: ContentChangeType.update,3364id: FAKE_SITE_PARENT_ID,3365version: null,3366ancestors: null,3367body: {3368storage: {3369representation: "storage",3370value: "fake content root index",3371},3372},3373fileName: "index.xml",3374status: "current",3375title: "Root Index",3376type: "page",3377},3378];3379const actual: ConfluenceSpaceChange[] = flattenIndexes(3380spaceChanges,3381FAKE_METADATA_EMPTY,3382FAKE_SITE_PARENT_ID3383);3384assertEquals(expected, actual);3385});33863387test(suiteLabel("update_one_folder_with_index"), async () => {3388const spaceChanges: ConfluenceSpaceChange[] = [3389{3390contentChangeType: ContentChangeType.create,3391ancestors: [3392{3393id: "fake-parent-id",3394},3395],3396body: {3397storage: {3398representation: "storage",3399value: "fake-index-value-update",3400},3401},3402fileName: "fake-parent/index.xml",3403space: {3404key: "fake-space-key",3405id: "fake-space-id",3406homepage: buildFakeContent(),3407},3408status: "current",3409title: "fake-index-title-update",3410type: "page",3411},3412];3413const expected: ConfluenceSpaceChange[] = [3414{3415contentChangeType: ContentChangeType.update,3416id: "fake-parent-id",3417version: null,3418ancestors: [3419{3420id: "fake-space-id",3421},3422],3423body: {3424storage: {3425representation: "storage",3426value: "fake-index-value-update",3427},3428},3429fileName: "fake-parent",3430status: "current",3431title: "fake-index-title-update",3432type: "page",3433},3434];3435const actual: ConfluenceSpaceChange[] = flattenIndexes(3436spaceChanges,3437FAKE_METADATA_ONE_FOLDER,3438FAKE_SITE_PARENT_ID3439);3440assertEquals(expected, actual);3441});34423443test(suiteLabel("one_multinested_folder_with_indexes"), async () => {3444const spaceChanges: ConfluenceSpaceChange[] = [3445{3446contentChangeType: ContentChangeType.create,3447ancestors: [3448{3449id: "8781825",3450},3451],3452body: {3453storage: {3454representation: "storage",3455value: "",3456},3457},3458fileName: "fake-great-grand-parent",3459space: {3460key: "fake-space-key",3461id: "fake-space-id",3462homepage: buildFakeContent(),3463},3464status: "current",3465title: "Fake-great-grand-parent",3466type: "page",3467},3468{3469contentChangeType: ContentChangeType.create,3470ancestors: [3471{3472id: "fake-great-grand-parent",3473},3474],3475body: {3476storage: {3477representation: "storage",3478value: "",3479},3480},3481fileName: "fake-great-grand-parent/fake-grand-parent",3482space: {3483key: "fake-space-key",3484id: "fake-space-id",3485homepage: buildFakeContent(),3486},3487status: "current",3488title: "Fake-grand-parent",3489type: "page",3490},3491{3492contentChangeType: ContentChangeType.create,3493ancestors: [3494{3495id: "fake-great-grand-parent/fake-grand-parent",3496},3497],3498body: {3499storage: {3500representation: "storage",3501value: "",3502},3503},3504fileName: "fake-great-grand-parent/fake-grand-parent/fake-parent",3505space: {3506key: "fake-space-key",3507id: "fake-space-id",3508homepage: buildFakeContent(),3509},3510status: "current",3511title: "Fake-parent",3512type: "page",3513},3514{3515contentChangeType: ContentChangeType.create,3516ancestors: [3517{3518id: "fake-great-grand-parent/fake-grand-parent/fake-parent",3519},3520],3521body: {3522storage: {3523representation: "storage",3524value: "fake-value",3525},3526},3527fileName:3528"fake-great-grand-parent/fake-grand-parent/fake-parent/index.xml",3529space: {3530key: "fake-space-key",3531id: "fake-space-id",3532homepage: buildFakeContent(),3533},3534status: "current",3535title: "fake-title",3536type: "page",3537},3538];3539const expected: ConfluenceSpaceChange[] = [3540{3541contentChangeType: ContentChangeType.create,3542ancestors: [3543{3544id: "8781825",3545},3546],3547body: {3548storage: {3549representation: "storage",3550value: "",3551},3552},3553fileName: "fake-great-grand-parent",3554space: {3555key: "fake-space-key",3556id: "fake-space-id",3557homepage: buildFakeContent(),3558},3559status: "current",3560title: "Fake-great-grand-parent",3561type: "page",3562},3563{3564contentChangeType: ContentChangeType.create,3565ancestors: [3566{3567id: "fake-great-grand-parent",3568},3569],3570body: {3571storage: {3572representation: "storage",3573value: "",3574},3575},3576fileName: "fake-great-grand-parent/fake-grand-parent",3577space: {3578key: "fake-space-key",3579id: "fake-space-id",3580homepage: buildFakeContent(),3581},3582status: "current",3583title: "Fake-grand-parent",3584type: "page",3585},3586{3587contentChangeType: ContentChangeType.create,3588ancestors: [3589{3590id: "fake-great-grand-parent/fake-grand-parent",3591},3592],3593body: {3594storage: {3595representation: "storage",3596value: "fake-value",3597},3598},3599fileName: "fake-great-grand-parent/fake-grand-parent/fake-parent",3600space: {3601key: "fake-space-key",3602id: "fake-space-id",3603homepage: buildFakeContent(),3604},3605status: "current",3606title: "fake-title",3607type: "page",3608},3609];3610const actual: ConfluenceSpaceChange[] = flattenIndexes(3611spaceChanges,3612FAKE_METADATA_EMPTY,3613FAKE_SITE_PARENT_ID3614);3615assertEquals(expected, actual);3616});3617};36183619const runBuildFileToMetaTable = () => {3620const suiteLabel = (label: string) => `BuildFileToMetaTable_${label}`;36213622const fakeSite: SitePage[] = [3623{3624title: "Issue Triage",3625id: "19890180",3626metadata: {3627fileName: "triage.xml",3628},3629},3630{3631title: "Release Planning",3632id: "19890228",3633metadata: { fileName: "release-planning.xml" },3634},3635{3636title: "Team",3637id: "19857455",3638metadata: { fileName: "team.xml" },3639},3640];36413642const fakeSite_ONE: SitePage[] = [3643{3644title: "Issue Triage",3645id: "19890180",3646metadata: {3647fileName: "triage.xml",3648},3649},3650];36513652const check = (expected: Record<string, SitePage>, site: SitePage[]) => {3653assertEquals(expected, buildFileToMetaTable(site));3654};36553656test(suiteLabel("no_files"), async () => {3657const expected = {};3658const site: SitePage[] = [];3659check(expected, site);3660});36613662test(suiteLabel("one_file"), async () => {3663const expected = {3664["triage.qmd"]: {3665title: "Issue Triage",3666id: "19890180",3667metadata: {3668fileName: "triage.xml",3669},3670},3671};3672check(expected, fakeSite_ONE);3673});36743675test(suiteLabel("multiple"), async () => {3676const expected = {3677["release-planning.qmd"]: {3678title: "Release Planning",3679id: "19890228",3680metadata: { fileName: "release-planning.xml" },3681},3682["team.qmd"]: {3683title: "Team",3684id: "19857455",3685metadata: { fileName: "team.xml" },3686},3687["triage.qmd"]: {3688title: "Issue Triage",3689id: "19890180",3690metadata: {3691fileName: "triage.xml",3692},3693},3694};3695check(expected, fakeSite);3696});3697};36983699const runExtractLinks = () => {3700const suiteLabel = (label: string) => `ExtractLinks_${label}`;37013702const extractLinks = (value: string): ExtractedLink[] => {3703const links: string[] = value.match(LINK_FINDER) ?? [];3704const extractedLinks: ExtractedLink[] = links.map(3705(link: string): ExtractedLink => {3706const fileMatches = link.match(FILE_FINDER);3707const file = fileMatches ? fileMatches[0] ?? "" : "";3708return { link, file: `${file}.qmd` };3709}3710);3711return extractedLinks;3712};37133714const check = (expected: ExtractedLink[], value: string) => {3715assertEquals(expected, extractLinks(value));3716};37173718test(suiteLabel("empty_string"), async () => {3719const value = "";3720const expected: ExtractedLink[] = [];3721check(expected, value);3722});37233724test(suiteLabel("three_links"), async () => {3725const value =3726"<a href='no-replace.qmd'/>no</a> content content <a href='team.qmd#Fake-Anchor'>team</a> content content <a href='zqmdzz.qmd'>team</a>";3727const expected: ExtractedLink[] = [3728{ link: "href='no-replace.qmd'", file: "no-replace.qmd" },3729{ link: "href='team.qmd#Fake-Anchor'", file: "team.qmd" },3730{ link: "href='zqmdzz.qmd'", file: "zqmdzz.qmd" },3731];3732check(expected, value);3733});37343735test(suiteLabel("three_links_messy"), async () => {3736const value =3737"no-replace.qmd <a href='no-replace.qmd'/>no</a> no-replace.qmd content content <a href='team.qmd#Fake-Anchor'>team</a> content content <a href='zqmdzz.qmd'>team</a> qmd.qmd <a href='team.txt'>team</a>";3738const expected: ExtractedLink[] = [3739{ link: "href='no-replace.qmd'", file: "no-replace.qmd" },3740{ link: "href='team.qmd#Fake-Anchor'", file: "team.qmd" },3741{ link: "href='zqmdzz.qmd'", file: "zqmdzz.qmd" },3742];3743check(expected, value);3744});3745};37463747const runUpdateLinks = () => {3748const suiteLabel = (label: string) => `UpdateLinks_${label}`;37493750const fileMetadataTable = {3751["release-planning.qmd"]: {3752title: "Release Planning",3753id: "19890228",3754metadata: { fileName: "release-planning.xml" },3755},3756["team.qmd"]: {3757title: "Team",3758id: "19857455",3759metadata: { fileName: "team.xml" },3760},3761["triage.qmd"]: {3762title: "Issue Triage",3763id: "19890180",3764metadata: {3765fileName: "triage.xml",3766},3767},3768["authoring/hello-world5.qmd"]: {3769title: "Hello World5",3770id: "43417628",3771metadata: { editor: "v2", fileName: "authoring/hello-world5.xml" },3772},3773["special-characters.qmd"]: {3774title: "Special Characters !@#$%^&*(){}|?//\\",3775id: "123456",3776metadata: { fileName: "special-characters.xml" },3777},3778["folder/index.qmd"]: {3779title: "fake-index-title",3780id: "fake-index-id",3781metadata: { editor: "v2", fileName: "folder/index.xml" },3782},3783["folder"]: {3784title: "fake-folder-title",3785id: "fake-folder-id",3786metadata: { editor: "v2", fileName: "folder" },3787},3788};37893790const UPDATE_NO_LINKS: ContentUpdate = {3791contentChangeType: ContentChangeType.update,3792id: "19890228",3793version: null,3794title: "Release Planning",3795type: "page",3796status: "current",3797ancestors: [{ id: "19759105" }],3798body: {3799storage: {3800value: "no links",3801representation: "storage",3802},3803},3804fileName: "release-planning.xml",3805};38063807const UPDATE_LINKS_ONE: ContentUpdate = {3808contentChangeType: ContentChangeType.update,3809id: "19890228",3810version: null,3811title: "Release Planning",3812type: "page",3813status: "current",3814ancestors: [{ id: "19759105" }],3815body: {3816storage: {3817value:3818"<a href='no-replace.qmd'/>no</a> content content <a href='team.qmd'>team</a> content content <a href='zqmdzz.qmd'>team</a>",3819representation: "storage",3820},3821},3822fileName: "release-planning.xml",3823};38243825const UPDATE_LINK_TO_INDEX: ContentUpdate = {3826contentChangeType: ContentChangeType.update,3827id: "19890228",3828version: null,3829title: "Release Planning",3830type: "page",3831status: "current",3832ancestors: [{ id: "19759105" }],3833body: {3834storage: {3835value: "<a href='folder/index.qmd'>team</a>",3836representation: "storage",3837},3838},3839fileName: "release-planning.xml",3840};38413842const UPDATE_SELF_LINK_FROM_INDEX: ContentUpdate = {3843contentChangeType: ContentChangeType.update,3844id: "fake-folder-id",3845version: null,3846title: "fake-folder-title",3847type: "page",3848status: "current",3849ancestors: [{ id: "19759105" }],3850body: {3851storage: {3852value: "<a href='index.qmd'>self</a>",3853representation: "storage",3854},3855},3856fileName: "folder",3857};38583859const UPDATE_LINKS_SPECIAL_CHAR: ContentUpdate = {3860contentChangeType: ContentChangeType.update,3861id: "19890228",3862version: null,3863title: "Release Planning",3864type: "page",3865status: "current",3866ancestors: [{ id: "19759105" }],3867body: {3868storage: {3869value: "<a href='special-characters.qmd'>team</a>",3870representation: "storage",3871},3872},3873fileName: "release-planning.xml",3874};38753876const UPDATE_LINKS_ONE_NESTED_DOT_SLASH: ContentUpdate = {3877contentChangeType: ContentChangeType.update,3878id: "43778049",3879version: null,3880title: "Links2",3881type: "page",3882status: "current",3883ancestors: [{ id: "42336414" }],3884body: {3885storage: {3886value: `<a href='./hello-world5.qmd'>Hello World 5</a>`,3887representation: "storage",3888},3889},3890fileName: "authoring/links2.xml",3891};38923893const UPDATE_LINKS_ONE_NESTED: ContentUpdate = {3894contentChangeType: ContentChangeType.update,3895id: "43778049",3896version: null,3897title: "Links2",3898type: "page",3899status: "current",3900ancestors: [{ id: "42336414" }],3901body: {3902storage: {3903value: `<a href='hello-world5.qmd'>Hello World 5</a>`,3904representation: "storage",3905},3906},3907fileName: "authoring/links2.xml",3908};39093910const UPDATE_LINKS_ONE_NESTED_ABS: ContentUpdate = {3911contentChangeType: ContentChangeType.update,3912id: "43778049",3913version: null,3914title: "Links2",3915type: "page",3916status: "current",3917ancestors: [{ id: "42336414" }],3918body: {3919storage: {3920value: `<a href='/release-planning.qmd'>Release Planning</a>`,3921representation: "storage",3922},3923},3924fileName: "authoring/links2.xml",3925};39263927const UPDATE_LINKS_ONE_ANCHOR: ContentUpdate = {3928contentChangeType: ContentChangeType.update,3929id: "19890228",3930version: null,3931title: "Release Planning",3932type: "page",3933status: "current",3934ancestors: [{ id: "19759105" }],3935body: {3936storage: {3937value:3938"<a href='no-replace.qmd'/>no</a> content content <a href='team.qmd#Fake-Anchor'>team</a> content content <a href='zqmdzz.qmd'>team</a>",3939representation: "storage",3940},3941},3942fileName: "release-planning.xml",3943};39443945const UPDATE_LINKS_SEVERAL: ContentUpdate = {3946contentChangeType: ContentChangeType.update,3947id: "19890228",3948version: null,3949title: "Release Planning",3950type: "page",3951status: "current",3952ancestors: [{ id: "19759105" }],3953body: {3954storage: {3955value:3956"<a href='no-replace.qmd'/>not-found</a> content content <a href='team.qmd'>teamz</a> content content <a href='zqmdzz.qmd'>not-found</a> <a href='release-planning.qmd'>Do the Release Planning</a> and then triage.qmd .qmd <a href='triage.qmd'>triage.qmd</a>",3957representation: "storage",3958},3959},3960fileName: "release-planning.xml",3961};39623963const check = (3964expectedPass1Changes: ConfluenceSpaceChange[],3965changes: ConfluenceSpaceChange[],3966fileMetadataTable: Record<string, SitePage>,3967server = "fake-server",3968parent = FAKE_PARENT,3969expectedPass2Changes: ConfluenceSpaceChange[] = []3970) => {3971const { pass1Changes, pass2Changes } = updateLinks(3972fileMetadataTable,3973changes,3974server,3975parent3976);39773978assertEquals(expectedPass1Changes, pass1Changes);3979assertEquals(expectedPass2Changes, pass2Changes);3980};39813982test(suiteLabel("no_files"), async () => {3983const changes: ConfluenceSpaceChange[] = [];3984const expected: ConfluenceSpaceChange[] = [];3985check(expected, changes, fileMetadataTable);3986});39873988test(suiteLabel("one_update_noLink"), async () => {3989const changes: ConfluenceSpaceChange[] = [UPDATE_NO_LINKS];3990const expected: ConfluenceSpaceChange[] = [UPDATE_NO_LINKS];3991check(expected, changes, fileMetadataTable);3992});39933994test(suiteLabel("one_update_link"), async () => {3995const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_ONE];3996const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";3997const expectedUpdate: ContentUpdate = {3998...UPDATE_LINKS_ONE,3999body: {4000storage: {4001value: `<a href=\'no-replace.qmd\'/>no</a> content content <a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/19857455'>team</a> content content <a href=\'zqmdzz.qmd\'>team</a>`,4002representation: "storage",4003},4004},4005};4006const expected: ConfluenceSpaceChange[] = [expectedUpdate];4007const expectedPass2Changes: ConfluenceSpaceChange[] = [expectedUpdate];4008check(4009expected,4010changes,4011fileMetadataTable,4012"fake-server",4013FAKE_PARENT,4014expectedPass2Changes4015);4016});40174018test(suiteLabel("one_update_link_index"), async () => {4019const changes: ConfluenceSpaceChange[] = [UPDATE_LINK_TO_INDEX];4020const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4021const expectedUpdate: ContentUpdate = {4022...UPDATE_LINK_TO_INDEX,4023body: {4024storage: {4025value: `<a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/fake-folder-id'>team</a>`,4026representation: "storage",4027},4028},4029};4030const expected: ConfluenceSpaceChange[] = [expectedUpdate];4031check(expected, changes, fileMetadataTable, "fake-server", FAKE_PARENT);4032});40334034test(suiteLabel("one_update_link_from_index"), async () => {4035const changes: ConfluenceSpaceChange[] = [UPDATE_SELF_LINK_FROM_INDEX];4036const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4037const expectedUpdate: ContentUpdate = {4038...UPDATE_SELF_LINK_FROM_INDEX,4039body: {4040storage: {4041value: `<a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/fake-index-id'>self</a>`,4042representation: "storage",4043},4044},4045};4046const expected: ConfluenceSpaceChange[] = [expectedUpdate];4047check(expected, changes, fileMetadataTable, "fake-server", FAKE_PARENT);4048});40494050test(suiteLabel("one_update_link_special_char"), async () => {4051const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_SPECIAL_CHAR];4052const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4053const expectedUpdate: ContentUpdate = {4054...UPDATE_LINKS_ONE,4055body: {4056storage: {4057value: `<a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/123456'>team</a>`,4058representation: "storage",4059},4060},4061};4062const expected: ConfluenceSpaceChange[] = [expectedUpdate];4063check(expected, changes, fileMetadataTable, "fake-server", FAKE_PARENT);4064});40654066test(suiteLabel("one_update_link_nested_dot_slash"), async () => {4067const changes: ConfluenceSpaceChange[] = [4068UPDATE_LINKS_ONE_NESTED_DOT_SLASH,4069];4070const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4071const expectedUpdate: ContentUpdate = {4072...UPDATE_LINKS_ONE_NESTED_DOT_SLASH,4073body: {4074storage: {4075value: `<a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/43417628'>Hello World 5</a>`,4076representation: "storage",4077},4078},4079};4080const expected: ConfluenceSpaceChange[] = [expectedUpdate];4081check(expected, changes, fileMetadataTable);4082});40834084test(suiteLabel("one_update_link_nested_relative"), async () => {4085const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_ONE_NESTED];4086const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4087const expectedUpdate: ContentUpdate = {4088...UPDATE_LINKS_ONE_NESTED,4089body: {4090storage: {4091value: `<a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/43417628'>Hello World 5</a>`,4092representation: "storage",4093},4094},4095};4096const expected: ConfluenceSpaceChange[] = [expectedUpdate];4097check(expected, changes, fileMetadataTable);4098});40994100test(suiteLabel("one_update_link_nested_absolute"), async () => {4101const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_ONE_NESTED_ABS];4102const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4103const expectedUpdate: ContentUpdate = {4104...UPDATE_LINKS_ONE_NESTED_ABS,4105body: {4106storage: {4107value: `<a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/19890228'>Release Planning</a>`,4108representation: "storage",4109},4110},4111};4112const expected: ConfluenceSpaceChange[] = [expectedUpdate];4113check(expected, changes, fileMetadataTable);4114});41154116test(suiteLabel("one_update_link_anchor"), async () => {4117const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_ONE_ANCHOR];4118const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4119const expectedUpdate: ContentUpdate = {4120...UPDATE_LINKS_ONE,4121body: {4122storage: {4123value:4124"<a href='no-replace.qmd'/>no</a> content content <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19857455#Fake-Anchor'>team</a> content content <a href='zqmdzz.qmd'>team</a>",4125representation: "storage",4126},4127},4128};4129const expected: ConfluenceSpaceChange[] = [expectedUpdate];4130const expectedSecondPassChanges: ConfluenceSpaceChange[] = [expectedUpdate];4131check(4132expected,4133changes,4134fileMetadataTable,4135"fake-server",4136FAKE_PARENT,4137expectedSecondPassChanges4138);4139});41404141test(suiteLabel("one_change_several_update_links"), async () => {4142const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_SEVERAL];4143const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4144const expectedUpdate: ContentUpdate = {4145...UPDATE_LINKS_ONE,4146body: {4147storage: {4148value: `<a href='no-replace.qmd'/>not-found</a> content content <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19857455'>teamz</a> content content <a href='zqmdzz.qmd'>not-found</a> <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19890228'>Do the Release Planning</a> and then triage.qmd .qmd <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19890180'>triage.qmd</a>`,4149representation: "storage",4150},4151},4152};4153const expected: ConfluenceSpaceChange[] = [expectedUpdate];4154const expectedSecondPassChanges: ConfluenceSpaceChange[] = [expectedUpdate];4155check(4156expected,4157changes,4158fileMetadataTable,4159"fake-server",4160FAKE_PARENT,4161expectedSecondPassChanges4162);4163});41644165test(suiteLabel("two_changes_several_update_links"), async () => {4166const changes: ConfluenceSpaceChange[] = [4167UPDATE_LINKS_SEVERAL,4168UPDATE_LINKS_ONE,4169];4170const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4171const expectedUpdateSeveralLinks: ContentUpdate = {4172...UPDATE_LINKS_ONE,4173body: {4174storage: {4175value: `<a href='no-replace.qmd'/>not-found</a> content content <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19857455'>teamz</a> content content <a href='zqmdzz.qmd'>not-found</a> <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19890228'>Do the Release Planning</a> and then triage.qmd .qmd <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19890180'>triage.qmd</a>`,4176representation: "storage",4177},4178},4179};41804181const expectedUpdateOneLink: ContentUpdate = {4182...UPDATE_LINKS_ONE,4183body: {4184storage: {4185value: `<a href='no-replace.qmd'/>no</a> content content <a href='fake-server/wiki/spaces/QUARTOCONF/pages/19857455'>team</a> content content <a href='zqmdzz.qmd'>team</a>`,4186representation: "storage",4187},4188},4189};4190const expected: ConfluenceSpaceChange[] = [4191expectedUpdateSeveralLinks,4192expectedUpdateOneLink,4193];4194const expectedSecondPassChanges: ConfluenceSpaceChange[] = [4195expectedUpdateSeveralLinks,4196expectedUpdateOneLink,4197];4198check(4199expected,4200changes,4201fileMetadataTable,4202"fake-server",4203FAKE_PARENT,4204expectedSecondPassChanges4205);4206});4207};42084209const runConvertForSecondPass = () => {4210const suiteLabel = (label: string) => `ConvertForSecondPass_${label}`;42114212const fileMetadataTable = {4213["release-planning.qmd"]: {4214title: "Release Planning",4215id: "19890228",4216metadata: { fileName: "release-planning.xml" },4217},4218["team.qmd"]: {4219title: "Team",4220id: "19857455",4221metadata: { fileName: "team.xml" },4222},4223["team.test.qmd"]: {4224title: "TeamTest",4225id: "19857456",4226metadata: { fileName: "team.test.xml" },4227},4228["triage.qmd"]: {4229title: "Issue Triage",4230id: "19890180",4231metadata: {4232fileName: "triage.xml",4233},4234},4235["authoring/hello-world5.qmd"]: {4236title: "Hello World5",4237id: "43417628",4238metadata: { editor: "v2", fileName: "authoring/hello-world5.xml" },4239},4240};42414242const fakeSpace: Space = {4243key: "fake-space-key",4244id: "fake-space-id",4245homepage: buildFakeContent(),4246};42474248const UPDATE_NO_LINKS: ContentUpdate = {4249contentChangeType: ContentChangeType.update,4250id: "19890228",4251version: null,4252title: "Release Planning",4253type: "page",4254status: "current",4255ancestors: [{ id: "19759105" }],4256body: {4257storage: {4258value: "no links",4259representation: "storage",4260},4261},4262fileName: "release-planning.xml",4263};42644265const CREATE_NO_LINKS: ContentCreate = {4266contentChangeType: ContentChangeType.create,4267title: "Release Planning",4268type: "page",4269status: "current",4270ancestors: [{ id: "19759105" }],4271space: fakeSpace,4272body: {4273storage: {4274value: "no links",4275representation: "storage",4276},4277},4278fileName: "release-planning.xml",4279};42804281const UPDATE_LINKS_ONE: ContentUpdate = {4282contentChangeType: ContentChangeType.update,4283id: "19890228",4284version: null,4285title: "Release Planning",4286type: "page",4287status: "current",4288ancestors: [{ id: "19759105" }],4289body: {4290storage: {4291value:4292"<a href='no-replace.qmd'/>no</a> content content <a href='team.qmd'>team</a> content content <a href='zqmdzz.qmd'>team</a>",4293representation: "storage",4294},4295},4296fileName: "release-planning.xml",4297};42984299const UPDATE_LINKS_ONE_DOUBLE_EXT: ContentUpdate = {4300...UPDATE_LINKS_ONE,4301body: {4302storage: {4303value:4304"<a href='no-replace.qmd'/>no</a> content content <a href='team.test.qmd'>team</a> content content <a href='zqmdzz.qmd'>team</a>",4305representation: "storage",4306},4307},4308};43094310const CREATE_LINKS_ONE: ContentCreate = {4311contentChangeType: ContentChangeType.create,4312title: "Release Planning",4313type: "page",4314status: "current",4315ancestors: [{ id: "19759105" }],4316space: fakeSpace,4317body: {4318storage: {4319value:4320"<a href='no-replace.qmd'/>no</a> content content <a href='team.qmd'>team</a> content content <a href='zqmdzz.qmd'>team</a>",4321representation: "storage",4322},4323},4324fileName: "release-planning.xml",4325};43264327const UPDATE_LINKS_ONE_NESTED_DOT_SLASH: ContentUpdate = {4328contentChangeType: ContentChangeType.update,4329id: "43778049",4330version: null,4331title: "Links2",4332type: "page",4333status: "current",4334ancestors: [{ id: "42336414" }],4335body: {4336storage: {4337value: `<a href='./hello-world5.qmd'>Hello World 5</a>`,4338representation: "storage",4339},4340},4341fileName: "authoring/links2.xml",4342};43434344const UPDATE_LINKS_ONE_NESTED: ContentUpdate = {4345contentChangeType: ContentChangeType.update,4346id: "43778049",4347version: null,4348title: "Links2",4349type: "page",4350status: "current",4351ancestors: [{ id: "42336414" }],4352body: {4353storage: {4354value: `<a href='hello-world5.qmd'>Hello World 5</a>`,4355representation: "storage",4356},4357},4358fileName: "authoring/links2.xml",4359};43604361const UPDATE_LINKS_ONE_NESTED_ABS: ContentUpdate = {4362contentChangeType: ContentChangeType.update,4363id: "43778049",4364version: null,4365title: "Links2",4366type: "page",4367status: "current",4368ancestors: [{ id: "42336414" }],4369body: {4370storage: {4371value: `<a href='/release-planning.qmd'>Release Planning</a>`,4372representation: "storage",4373},4374},4375fileName: "authoring/links2.xml",4376};43774378const UPDATE_LINKS_ONE_ANCHOR: ContentUpdate = {4379contentChangeType: ContentChangeType.update,4380id: "19890228",4381version: null,4382title: "Release Planning",4383type: "page",4384status: "current",4385ancestors: [{ id: "19759105" }],4386body: {4387storage: {4388value:4389"<a href='no-replace.qmd'/>no</a> content content <a href='team.qmd#Fake-Anchor'>team</a> content content <a href='zqmdzz.qmd'>team</a>",4390representation: "storage",4391},4392},4393fileName: "release-planning.xml",4394};43954396const UPDATE_LINKS_SEVERAL: ContentUpdate = {4397contentChangeType: ContentChangeType.update,4398id: "19890228",4399version: null,4400title: "Release Planning",4401type: "page",4402status: "current",4403ancestors: [{ id: "19759105" }],4404body: {4405storage: {4406value:4407"<a href='no-replace.qmd'/>not-found</a> content content <a href='team.qmd'>teamz</a> content content <a href='zqmdzz.qmd'>not-found</a> <a href='release-planning.qmd'>Do the Release Planning</a> and then triage.qmd .qmd <a href='triage.qmd'>triage.qmd</a>",4408representation: "storage",4409},4410},4411fileName: "release-planning.xml",4412};44134414const check = (4415expected: ConfluenceSpaceChange[] = [],4416changes: ConfluenceSpaceChange[],4417fileMetadataTable: Record<string, SitePage>,4418server = "fake-server",4419parent = FAKE_PARENT4420) => {4421const result = convertForSecondPass(4422fileMetadataTable,4423changes,4424server,4425parent4426);4427assertEquals(expected, result);4428};44294430test(suiteLabel("no_files"), async () => {4431const changes: ConfluenceSpaceChange[] = [];4432const expected: ConfluenceSpaceChange[] = [];4433check(expected, changes, fileMetadataTable);4434});44354436test(suiteLabel("one_update_noLink"), async () => {4437const changes: ConfluenceSpaceChange[] = [UPDATE_NO_LINKS];4438const expected: ConfluenceSpaceChange[] = [UPDATE_NO_LINKS];4439check(expected, changes, fileMetadataTable);4440});44414442test(suiteLabel("one_create_noLink_convert_to_update"), async () => {4443const changes: ConfluenceSpaceChange[] = [CREATE_NO_LINKS];4444const expected: ConfluenceSpaceChange[] = [UPDATE_NO_LINKS];4445check(expected, changes, fileMetadataTable);4446});44474448test(suiteLabel("one_update_link"), async () => {4449const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_ONE];4450const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4451const expectedUpdate: ContentUpdate = {4452...UPDATE_LINKS_ONE,4453body: {4454storage: {4455value: `<a href=\'no-replace.qmd\'/>no</a> content content <a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/19857455'>team</a> content content <a href=\'zqmdzz.qmd\'>team</a>`,4456representation: "storage",4457},4458},4459};4460const expected: ConfluenceSpaceChange[] = [expectedUpdate];4461check(expected, changes, fileMetadataTable, "fake-server", FAKE_PARENT);4462});44634464test(suiteLabel("one_update_link_double_QMD"), async () => {4465const changes: ConfluenceSpaceChange[] = [UPDATE_LINKS_ONE_DOUBLE_EXT];4466const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4467const expectedUpdate: ContentUpdate = {4468...UPDATE_LINKS_ONE,4469body: {4470storage: {4471value: `<a href=\'no-replace.qmd\'/>no</a> content content <a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/19857456'>team</a> content content <a href=\'zqmdzz.qmd\'>team</a>`,4472representation: "storage",4473},4474},4475};4476const expected: ConfluenceSpaceChange[] = [expectedUpdate];4477check(expected, changes, fileMetadataTable, "fake-server", FAKE_PARENT);4478});44794480test(suiteLabel("one_update_link_from_create"), async () => {4481const changes: ConfluenceSpaceChange[] = [CREATE_LINKS_ONE];4482const rootURL = "fake-server/wiki/spaces/QUARTOCONF/pages";4483const expectedUpdate: ContentUpdate = {4484...UPDATE_LINKS_ONE,4485body: {4486storage: {4487value: `<a href=\'no-replace.qmd\'/>no</a> content content <a href=\'fake-server/wiki/spaces/QUARTOCONF/pages/19857455'>team</a> content content <a href=\'zqmdzz.qmd\'>team</a>`,4488representation: "storage",4489},4490},4491};4492const expected: ConfluenceSpaceChange[] = [expectedUpdate];4493check(expected, changes, fileMetadataTable, "fake-server", FAKE_PARENT);4494});4495};44964497const runFindAttachments = () => {4498const suiteLabel = (label: string) => `FindAttachments_${label}`;44994500const check = (4501expected: string[],4502bodyValue: string,4503filePaths: string[] = [],4504path: string = ""4505) => {4506assertEquals(4507JSON.stringify(expected),4508JSON.stringify(findAttachments(bodyValue, filePaths, path))4509);4510};45114512test(suiteLabel("empty"), async () => {4513const bodyValue: string = "";4514const expected: string[] = [];4515check(expected, bodyValue);4516});45174518test(suiteLabel("no_attachment"), async () => {4519const bodyValue: string = "fake body value";4520const expected: string[] = [];4521check(expected, bodyValue);4522});45234524test(suiteLabel("no_attachment_CDATA"), async () => {4525const bodyValue: string =4526"<ac:plain-text-body> <]</ac:plain-text-body><ac:plain-text-body>]> </ac:plain-text-body>";4527const expected: string[] = [];4528check(expected, bodyValue);4529});45304531test(suiteLabel("single_image"), async () => {4532const bodyValue: string =4533'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />';4534const expected: string[] = ["elephant.png"];4535check(expected, bodyValue);4536});45374538test(suiteLabel("single_image_lookup"), async () => {4539const bodyValue: string =4540'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />';4541const filePaths: string[] = ["fake-path/elephant.png"];4542const expected: string[] = ["elephant.png"];4543check(expected, bodyValue, filePaths);4544});45454546test(suiteLabel("single_image_lookup_full_path"), async () => {4547const bodyValue: string =4548'<ri:attachment ri:filename="computations-r_files/figure-publish/fig-airquality-1.png" ri:version-at-save="1" />';4549const filePaths: string[] = [4550"computations/r/computations-r_files/figure-publish/fig-airquality-1.png",4551];4552const expected: string[] = [4553"computations/r/computations-r_files/figure-publish/fig-airquality-1.png",4554];4555check(expected, bodyValue, filePaths, "computations/r/index.xml");4556});45574558test(suiteLabel("single_image_lookup_full_path_win"), async () => {4559const bodyValue: string =4560'<ri:attachment ri:filename="computations-r_files/figure-publish/fig-airquality-1.png" ri:version-at-save="1" />';4561const filePaths: string[] = [4562"computations\\r\\computations-r_files\\figure-publish\\fig-airquality-1.png",4563];4564const expected: string[] = [4565"computations\\r\\computations-r_files\\figure-publish\\fig-airquality-1.png",4566];4567check(expected, bodyValue, filePaths, "computations\\r\\index.xml");4568});45694570test(suiteLabel("single_with_same_name_nested_win"), async () => {4571const filePaths: string[] = [4572"folder\\images\\elephant.png",4573"images\\elephant.png",4574];4575const bodyValue: string =4576'<ri:attachment ri:filename="images/elephant.png" />';4577const expected: string[] = ["images\\elephant.png"];4578const path = "index.xml";4579check(expected, bodyValue, filePaths, path);4580});45814582test(suiteLabel("single_with_same_name_nested_win2"), async () => {4583const filePaths: string[] = [4584"folder\\images\\elephant.png",4585"images\\elephant.png",4586];4587const bodyValue: string =4588'<ri:attachment ri:filename="images/elephant.png" />';4589const expected: string[] = ["folder\\images\\elephant.png"];4590const path = "folder\\index.xml";4591check(expected, bodyValue, filePaths, path);4592});45934594test(suiteLabel("single_with_same_name_nested"), async () => {4595const filePaths: string[] = [4596"images/elephant.png",4597"folder/images/elephant.png",4598];4599const bodyValue: string =4600'<ri:attachment ri:filename="images/elephant.png" />';4601const expected: string[] = ["images/elephant.png"];4602const path = "index.xml";4603check(expected, bodyValue, filePaths, path);4604});46054606test(suiteLabel("single_image_lookup_relative_path"), async () => {4607const bodyValue: string =4608'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />';4609const filePaths: string[] = [4610"images/elephant.png",4611"parent/inner-parent/elephant.png",4612];4613const path = "parent/inner-parent/hello-world3.xml";4614const expected: string[] = ["parent/inner-parent/elephant.png"];4615check(expected, bodyValue, filePaths, path);4616});46174618test(suiteLabel("single_image_lookup_relative_path_win"), async () => {4619const bodyValue: string =4620'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />';4621const filePaths: string[] = [4622"images\\elephant.png",4623"parent\\inner-parent\\elephant.png",4624];4625const path = "parent\\inner-parent\\hello-world3.xml";4626const expected: string[] = ["parent\\inner-parent\\elephant.png"];4627check(expected, bodyValue, filePaths, path);4628});46294630test(suiteLabel("single_image_lookup_relative_path_win2"), async () => {4631const bodyValue: string =4632'<ri:attachment ri:filename="images/elephant.png" ri:version-at-save="1" />';4633const filePaths: string[] = ["folder\\images\\elephant.png"];4634const path = "folder/index.xml";4635const expected: string[] = ["folder\\images\\elephant.png"];4636check(expected, bodyValue, filePaths, path);4637});46384639test(suiteLabel("single_image_lookup_dupe_name"), async () => {4640const bodyValue: string =4641'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />';4642const filePaths: string[] = [4643"fake-path/elephant.png",4644"fake-path2/elephant.png",4645];4646const filePath = "fake-path2/file.xml";4647const expected: string[] = ["fake-path2/elephant.png"];4648check(expected, bodyValue, filePaths, filePath);4649});46504651test(suiteLabel("single_image_lookup_bad_paths"), async () => {4652const bodyValue: string =4653'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />';4654const filePaths: string[] = [4655"fake-path-fail/elephant.png",4656"fake-path2-fail/elephant.png",4657];4658const filePath = "fake-path2/file.xml";4659const expected: string[] = ["elephant.png"];4660check(expected, bodyValue, filePaths, filePath);4661});46624663test(suiteLabel("two_images"), async () => {4664const bodyValue: string =4665'<ri:attachment ri:filename="fake-image.png" ri:version-at-save="1" /> <ri:attachment ri:filename="fake-image2.png" ri:version-at-save="1" />';4666const expected: string[] = ["fake-image.png", "fake-image2.png"];4667check(expected, bodyValue);4668});46694670test(suiteLabel("two_images_with_dupes"), async () => {4671const bodyValue: string =4672'<ri:attachment ri:filename="fake-image.png" ri:version-at-save="1" /> <ri:attachment ri:filename="fake-image.png" ri:version-at-save="1" /> <ri:attachment ri:filename="fake-image2.png" ri:version-at-save="1" />';4673const expected: string[] = ["fake-image.png", "fake-image2.png"];4674check(expected, bodyValue);4675});46764677test(suiteLabel("image_not_attachment"), async () => {4678const bodyValue: string = '"elephant.png"';4679const expected: string[] = [];4680check(expected, bodyValue);4681});46824683test(suiteLabel("two_images_with_dupes_invalids"), async () => {4684const bodyValue: string =4685'<ri:attachment ri:filename="fake-image.png" ri:version-at-save="1" /> <ri:attachment ri:filename="fake-image.png" ri:version-at-save="1" /> <ri:attachment ri:filename="fake-image2.png" ri:version-at-save="1" /> no-match.png "no-match.png"';4686const expected: string[] = ["fake-image.png", "fake-image2.png"];4687check(expected, bodyValue);4688});46894690test(suiteLabel("audio_file"), async () => {4691const DOUBLE_BRACKET = "]]";4692const bodyValue: string = `<ac:link><ri:attachment ri:filename="audio/2022-11-10-intro-psychological-safety.m4a"/><ac:plain-text-link-body><![CDATA[audio/2022-11-10-intro-psychological-safety.m4a${DOUBLE_BRACKET}></ac:plain-text-link-body></ac:link>`;4693const expected: string[] = [4694"audio/2022-11-10-intro-psychological-safety.m4a",4695];4696check(expected, bodyValue);4697});46984699test(suiteLabel("svg_attachment"), async () => {4700//5815-bug-confluence-links-to-file-attachments-not-supported4701const DOUBLE_BRACKET = "]]";4702const bodyValue: string = `<ac:link><ri:attachment ri:filename="quarto-hex.svg"/><ac:plain-text-link-body><![CDATA[quarto-hex-svg${DOUBLE_BRACKET}></ac:plain-text-link-body></ac:link>`;4703const expected: string[] = [4704"quarto-hex.svg",4705];4706check(expected, bodyValue);4707});47084709test(suiteLabel("ai_attachment"), async () => {4710//5815-bug-confluence-links-to-file-attachments-not-supported4711const DOUBLE_BRACKET = "]]";4712const bodyValue: string = `<ac:link><ri:attachment ri:filename="quarto-hex.ai"/><ac:plain-text-link-body><![CDATA[quarto-hex-svg${DOUBLE_BRACKET}></ac:plain-text-link-body></ac:link>`;4713const expected: string[] = [4714"quarto-hex.ai",4715];4716check(expected, bodyValue);4717});47184719test(suiteLabel("pdf_attachment"), async () => {4720//5815-bug-confluence-links-to-file-attachments-not-supported4721const DOUBLE_BRACKET = "]]";4722const bodyValue: string = `<ac:link><ri:attachment ri:filename="quarto-hex.pdf"/><ac:plain-text-link-body><![CDATA[quarto-hex-svg${DOUBLE_BRACKET}></ac:plain-text-link-body></ac:link>`;4723const expected: string[] = [4724"quarto-hex.pdf",4725];4726check(expected, bodyValue);4727});4728};47294730const runUpdateImagePathsForContentBody = () => {4731const suiteLabel = (label: string) =>4732`UpdateImagePathsForContentBody_${label}`;47334734const UPDATE_NO_IMAGES: ContentBody = {4735storage: {4736value: "no-images",4737representation: "raw",4738},4739};47404741const UPDATE_ONE_FLAT_IMAGE: ContentBody = {4742storage: {4743value:4744'<ri:attachment ri:filename="elephant.png" ri:version-at-save="1" />',4745representation: "raw",4746},4747};4748const UPDATE_ONE_TO_FLATTEN_IMAGE: ContentBody = {4749storage: {4750value:4751'<ri:attachment ri:filename="a/b/c/elephant.png" ri:version-at-save="1" />',4752representation: "raw",4753},4754};47554756const check = (expected: ContentBody, bodyValue: ContentBody) => {4757assertEquals(expected, updateImagePaths(bodyValue));4758};47594760test(suiteLabel("no_images"), async () => {4761const changes = UPDATE_NO_IMAGES;4762const expected = UPDATE_NO_IMAGES;4763check(expected, changes);4764});47654766test(suiteLabel("images-already-flattened"), async () => {4767const changes = UPDATE_ONE_FLAT_IMAGE;4768const expected = UPDATE_ONE_FLAT_IMAGE;4769check(expected, changes);4770});47714772test(suiteLabel("images-to-flatten"), async () => {4773const changes = UPDATE_ONE_TO_FLATTEN_IMAGE;4774const expected = UPDATE_ONE_FLAT_IMAGE;4775check(expected, changes);4776});4777};47784779const runCapFirstLetter = () => {4780const suiteLabel = (label: string) => `CapFirstLetter_${label}`;4781test(suiteLabel("basic"), async () => {4782assertEquals("A", capitalizeFirstLetter("a"));4783});47844785test(suiteLabel("basic_space"), async () => {4786assertEquals("A b", capitalizeFirstLetter("a b"));4787});47884789test(suiteLabel("empty"), async () => {4790assertEquals("", capitalizeFirstLetter(""));4791});47924793test(suiteLabel("empty2"), async () => {4794assertEquals("", capitalizeFirstLetter());4795});4796};47974798const runFootnoteTransform = () => {4799const suiteLabel = (label: string) => `RunFootnoteTransform_${label}`;48004801test(suiteLabel("no_note"), async () => {4802const value = "<span>No Footnotes</span>";4803assertEquals(value, footnoteTransform(value));4804});48054806test(suiteLabel("one_note_forward"), async () => {4807const value =4808'<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>';48094810const expected =4811'<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fnref1</ac:parameter></ac:structured-macro><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>';4812const actual = footnoteTransform(value);48134814assertEquals(expected, footnoteTransform(value));4815});48164817test(suiteLabel("ignore_existing"), async () => {4818const value =4819'<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fnref1</ac:parameter></ac:structured-macro><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></p>' +4820'<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">' +4821"<hr />" +4822"<ol>" +4823'<li id="fn1"><p>Here is the footnote.<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fn1</ac:parameter></ac:structured-macro><a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>' +4824"</ol>" +4825"</section>";4826const actual = footnoteTransform(value);4827assertEquals(value, footnoteTransform(value));4828});48294830test(suiteLabel("one_note_forward_back"), async () => {4831const value =4832'<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></p>\n' +4833'<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">\n' +4834"<hr />\n" +4835"<ol>\n" +4836'<li id="fn1"><p>Here is the footnote.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>\n' +4837"</ol>\n" +4838"</section>";48394840const expected =4841'<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fnref1</ac:parameter></ac:structured-macro><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></p>\n' +4842'<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">\n' +4843"<hr />\n" +4844"<ol>\n" +4845'<li id="fn1"><p>Here is the footnote.<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fn1</ac:parameter></ac:structured-macro><a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>\n' +4846"</ol>\n" +4847"</section>";4848const actual = footnoteTransform(value);4849assertEquals(expected, actual);4850});48514852test(suiteLabel("two_notes"), async () => {4853const value =4854'<p>Here is a footnote reference,<a href="#fn1" class="footnote-ref"><sup>1</sup></a> and another.<a href="#fn2" class="footnote-ref"><sup>2</sup></a></p>\n' +4855"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam dapibus mattis malesuada. Sed fringilla posuere ultricies. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur scelerisque, nisi a consequat aliquet, metus augue feugiat augue, non eleifend lectus eros non nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Mauris sollicitudin auctor orci id vulputate. Pellentesque at luctus urna. Quisque pretium sapien at elit congue maximus. Mauris fermentum sapien eget justo elementum, eleifend auctor dui mattis. Morbi purus elit, auctor id magna et, blandit mattis nunc. Cras commodo leo ut ultrices semper. Nunc a libero dapibus, vestibulum velit sed, bibendum risus. Duis sollicitudin, libero ac sollicitudin maximus, ante massa blandit risus, non laoreet lectus justo non nisl.</p>\n" +4856"<hr />\n" +4857"<ol>\n" +4858" <li>\n" +4859' <p>Here is the footnote.<a href="#fnref1" class="footnote-back">↩︎</a></p>\n' +4860" </li>\n" +4861" <li>\n" +4862" <p>Here’s one with multiple blocks.</p>\n" +4863' <p>The whole paragraph can be indented, or just the first line. In this way, multi-paragraph footnotes work like multi-paragraph list items.<a href="#fnref2" class="footnote-back">↩︎</a></p>\n' +4864" </li>\n" +4865"</ol>";48664867const expected =4868'<p>Here is a footnote reference,<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fnref1</ac:parameter></ac:structured-macro><a href="#fn1" class="footnote-ref"><sup>1</sup></a> and another.<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fnref2</ac:parameter></ac:structured-macro><a href="#fn2" class="footnote-ref"><sup>2</sup></a></p>\n' +4869"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam dapibus mattis malesuada. Sed fringilla posuere ultricies. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur scelerisque, nisi a consequat aliquet, metus augue feugiat augue, non eleifend lectus eros non nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Mauris sollicitudin auctor orci id vulputate. Pellentesque at luctus urna. Quisque pretium sapien at elit congue maximus. Mauris fermentum sapien eget justo elementum, eleifend auctor dui mattis. Morbi purus elit, auctor id magna et, blandit mattis nunc. Cras commodo leo ut ultrices semper. Nunc a libero dapibus, vestibulum velit sed, bibendum risus. Duis sollicitudin, libero ac sollicitudin maximus, ante massa blandit risus, non laoreet lectus justo non nisl.</p>\n" +4870"<hr />\n" +4871"<ol>\n" +4872" <li>\n" +4873' <p>Here is the footnote.<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fn1</ac:parameter></ac:structured-macro><a href="#fnref1" class="footnote-back">↩︎</a></p>\n' +4874" </li>\n" +4875" <li>\n" +4876" <p>Here’s one with multiple blocks.</p>\n" +4877' <p>The whole paragraph can be indented, or just the first line. In this way, multi-paragraph footnotes work like multi-paragraph list items.<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">fn2</ac:parameter></ac:structured-macro><a href="#fnref2" class="footnote-back">↩︎</a></p>\n' +4878" </li>\n" +4879"</ol>";4880const actual = footnoteTransform(value);4881assertEquals(expected, actual);4882});4883};48844885if (HIDE_NOISE) {4886console.info(4887"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"4888);4889}48904891if (RUN_ALL_TESTS) {4892runGeneralTests();4893runFilterFilesForUpdate();4894runBuildSpaceChanges();4895runSpaceCreatesWithNesting();4896runSpaceUpdatesWithNesting();4897runMergeSitePages();4898runPublishRecordTests();4899runGetNextVersionTests();4900runWriteTokenComparator();4901runBuildContentCreate();4902runGetTitle();4903runBuildFileToMetaTable();4904runExtractLinks();4905runUpdateLinks();4906runConvertForSecondPass();4907runFindAttachments();4908runUpdateImagePathsForContentBody();4909runCapFirstLetter();4910runFootnoteTransform();4911runConfluenceParentFromString();4912runFlattenIndexes();4913} else {4914runFindAttachments();4915}491649174918