canvas_link_migrator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (C) 2023 - present Instructure, Inc.
5
+ #
6
+ # This file is part of Canvas.
7
+ #
8
+ # Canvas is free software: you can redistribute it and/or modify it under
9
+ # the terms of the GNU Affero General Public License as published by the Free
10
+ # Software Foundation, version 3 of the License.
11
+ #
12
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ # details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License along
18
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ module CanvasLinkMigrator
20
+ # This class encapsulates the logic to retrieve metadata (for various types of assets)
21
+ # given a migration id. This particular implementation relies on the migration object Canvas
22
+ # creates
23
+ #
24
+ # Each function returns exactly one id (if available), and nil if an id
25
+ # cannot be resolved
26
+ #
27
+ class ResourceMapService
28
+ attr_reader :migration_data
29
+
30
+ def initialize(migration_data)
31
+ @migration_data = migration_data
32
+ end
33
+
34
+ def resources
35
+ migration_data["resource_mapping"]
36
+ end
37
+
38
+ ### Overwritable methods
39
+ def supports_embedded_images
40
+ false
41
+ end
42
+
43
+ def fix_relative_urls?
44
+ true
45
+ end
46
+
47
+ def process_domain_substitutions(url)
48
+ url
49
+ end
50
+
51
+ def context_hosts
52
+ migration_data["destination_hosts"]
53
+ end
54
+
55
+ def attachment_path_id_lookup; end
56
+
57
+ def attachment_path_id_lookup_lower; end
58
+
59
+ def root_folder_name
60
+ migration_data["destination_root_folder"]
61
+ end
62
+ ### End of Ovewritable methods
63
+
64
+ # Returns the path for the context, for a course, it should return something like
65
+ # "courses/1"
66
+ def context_path
67
+ "/courses/#{migration_data["destination_course"]}"
68
+ end
69
+
70
+ # Looks up a wiki page slug for a migration id
71
+ def convert_wiki_page_migration_id_to_slug(migration_id)
72
+ resources.dig("wiki_pages", migration_id, "destination", "url")
73
+ end
74
+
75
+ # looks up a discussion topic
76
+ def convert_discussion_topic_migration_id(migration_id)
77
+ resources.dig("discussion_topics", migration_id, "destination", "id")
78
+ end
79
+
80
+ def convert_context_module_tag_migration_id(migration_id)
81
+ resources.dig("module_items", migration_id, "destination", "id")
82
+ end
83
+
84
+ def convert_attachment_migration_id(migration_id)
85
+ resources.dig("files", migration_id, "destination", "id")
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,3 @@
1
+ module CanvasLinkMigrator
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (C) 2023 - present Instructure, Inc.
5
+ #
6
+ # This file is part of Canvas.
7
+ #
8
+ # Canvas is free software: you can redistribute it and/or modify it under
9
+ # the terms of the GNU Affero General Public License as published by the Free
10
+ # Software Foundation, version 3 of the License.
11
+ #
12
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ # details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License along
18
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require "canvas_link_migrator/resource_map_service"
21
+ require "canvas_link_migrator/imported_html_converter"
22
+ require "canvas_link_migrator/link_parser"
23
+ require "canvas_link_migrator/link_replacer"
24
+ require "canvas_link_migrator/link_resolver"
25
+
26
+ module CanvasLinkMigrator
27
+ def self.relative_url?(url)
28
+ URI.parse(url).relative? && !url.to_s.start_with?("//")
29
+ rescue URI::Error
30
+ # leave the url as it was
31
+ false
32
+ end
33
+ end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (C) 2012 - present Instructure, Inc.
5
+ #
6
+ # This file is part of Canvas.
7
+ #
8
+ # Canvas is free software: you can redistribute it and/or modify it under
9
+ # the terms of the GNU Affero General Public License as published by the Free
10
+ # Software Foundation, version 3 of the License.
11
+ #
12
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ # details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License along
18
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require "spec_helper"
22
+ require "json"
23
+
24
+ describe CanvasLinkMigrator::ImportedHtmlConverter do
25
+ # tests link_parser and link_resolver
26
+
27
+ describe ".convert" do
28
+ before do
29
+ @path = "/courses/2/"
30
+ @converter = CanvasLinkMigrator::ImportedHtmlConverter.new(resource_map: JSON.parse(File.read("spec/fixtures/canvas_resource_map.json")))
31
+ end
32
+
33
+ it "converts a wiki reference" do
34
+ test_string = %(<a href="%24WIKI_REFERENCE%24/wiki/test-wiki-page?query=blah">Test Wiki Page</a>)
35
+ html, bad_links = @converter.convert_exported_html(test_string)
36
+ expect(html).to eq %(<a href="#{@path}pages/test-wiki-page?query=blah">Test Wiki Page</a>)
37
+ expect(bad_links).to be_nil
38
+ end
39
+
40
+ context "when course attachments exist" do
41
+ subject { @converter.convert_exported_html(test_string) }
42
+
43
+ let(:migration_id) { "E" }
44
+
45
+ context "and a data-download-url attribute references an icon maker icon" do
46
+ let(:test_string) do
47
+ %(<img src="$CANVAS_COURSE_REFERENCE$/file_ref/#{migration_id}/download?download_frd=1" alt="" data-inst-icon-maker-icon="true" data-download-url="$CANVAS_COURSE_REFERENCE$/file_ref/#{migration_id}/download?download_frd=1&icon_maker_icon=1">)
48
+ end
49
+
50
+ it "converts data-download-url for files without appending a context" do
51
+ html, bad_links = subject
52
+ expect(html).to eq(
53
+ "<img src=\"#{@path}files/5/download?download_frd=1\" alt=\"\" data-inst-icon-maker-icon=\"true\" data-download-url=\"/files/5/download?download_frd=1&icon_maker_icon=1\">"
54
+ )
55
+ expect(bad_links).to be_nil
56
+ end
57
+ end
58
+
59
+ # it "finds an attachment by migration id" do
60
+ # test_string = %{<p>This is an image: <br /><img src="%24CANVAS_OBJECT_REFERENCE%24/attachments/F" alt=":(" /></p>}
61
+ # expect(@converter.convert_exported_html(test_string)).to eq([%{<p>This is an image: <br><img src="#{@path}files/6/preview" alt=":("></p>}, nil])
62
+ # end
63
+
64
+ # it "finds an attachment by path" do
65
+ # test_string = %{<p>This is an image: <br /><img src="%24IMS_CC_FILEBASE%24/test.png" alt=":(" /></p>}
66
+
67
+ # # if there isn't a path->migration id map it'll be a relative course file path
68
+ # expect(@converter.convert_exported_html(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}file_contents/course%20files/test.png" alt=":("></p>}
69
+
70
+ # @migration.attachment_path_id_lookup = { "test.png" => att.migration_id }
71
+ # expect(@converter.convert_exported_html(test_string)).to eq %{<p>This is an image: <br><img src="#{@path}files/#{att.id}/preview" alt=":("></p>}
72
+ # end
73
+
74
+ # it "finds an attachment by a path with a space" do
75
+ # att = make_test_att
76
+ # @migration.attachment_path_id_lookup = { "subfolder/with a space/test.png" => att.migration_id }
77
+
78
+ # test_string = %(<img src="subfolder/with%20a%20space/test.png" alt="nope" />)
79
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
80
+
81
+ # test_string = %(<img src="subfolder/with+a+space/test.png" alt="nope" />)
82
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
83
+ # end
84
+
85
+ # it "finds an attachment even if the link has an extraneous folder" do
86
+ # att = make_test_att
87
+ # @migration.attachment_path_id_lookup = { "subfolder/test.png" => att.migration_id }
88
+
89
+ # test_string = %(<img src="anotherfolder/subfolder/test.png" alt="nope" />)
90
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
91
+ # end
92
+
93
+ # it "finds an attachment by path if capitalization is different" do
94
+ # att = make_test_att
95
+ # @migration.attachment_path_id_lookup = { "subfolder/withCapital/test.png" => "wrong!" }
96
+ # @migration.attachment_path_id_lookup_lower = { "subfolder/withcapital/test.png" => att.migration_id }
97
+
98
+ # test_string = %(<img src="subfolder/WithCapital/TEST.png" alt="nope" />)
99
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
100
+ # end
101
+
102
+ # it "finds an attachment with query params" do
103
+ # att = make_test_att
104
+ # @migration.attachment_path_id_lookup = { "test.png" => att.migration_id }
105
+
106
+ # test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_customaction=1&canvas_qs_customparam=1" alt="nope" />)
107
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/customaction?customparam=1" alt="nope">)
108
+
109
+ # test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?canvas_qs_customparam2=3" alt="nope" />)
110
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview?customparam2=3" alt="nope">)
111
+
112
+ # test_string = %(<img src="%24IMS_CC_FILEBASE%24/test.png?notarelevantparam" alt="nope" />)
113
+ # expect(@converter.convert_exported_html(test_string)).to eq %(<img src="#{@path}files/#{att.id}/preview" alt="nope">)
114
+ # end
115
+ end
116
+
117
+ it "converts picture source srcsets" do
118
+ test_string = %(<source srcset="$CANVAS_COURSE_REFERENCE$/img.src">)
119
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<source srcset="/courses/2/img.src">), nil])
120
+ end
121
+
122
+ it "converts a wiki reference without $ escaped" do
123
+ test_string = %(<a href="$WIKI_REFERENCE$/wiki/test-wiki-page?query=blah">Test Wiki Page</a>)
124
+
125
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}pages/test-wiki-page?query=blah">Test Wiki Page</a>), nil])
126
+ end
127
+
128
+ it "converts a wiki reference by migration id" do
129
+ test_string = %(<a href="wiki_page_migration_id=A">Test Wiki Page</a>)
130
+
131
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}pages/slug-a">Test Wiki Page</a>), nil])
132
+ end
133
+
134
+ it "converts a discussion reference by migration id" do
135
+ test_string = %(<a href="discussion_topic_migration_id=G">Test topic</a>)
136
+
137
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}discussion_topics/7">Test topic</a>), nil])
138
+ end
139
+
140
+ it "converts course section urls" do
141
+ test_string = %(<a href="%24CANVAS_COURSE_REFERENCE%24/discussion_topics">discussions</a>)
142
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="#{@path}discussion_topics">discussions</a>), nil])
143
+ end
144
+
145
+ it "leaves invalid and absolute urls alone" do
146
+ test_string = %(<a href="stupid &^%$ url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>)
147
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="stupid &amp;^%$ url">Linkage</a><br><a href="http://www.example.com/poop">Linkage</a>), nil])
148
+ end
149
+
150
+ it "leaves invalid mailto addresses alone" do
151
+ test_string = %(<a href="mailto:.">Bad mailto</a><br><a href="mailto:test@example.com">Good mailto</a>)
152
+ expect(@converter.convert_exported_html(test_string)).to eq(
153
+ [
154
+ %(<a href="mailto:.">Bad mailto</a><br><a href="mailto:test@example.com">Good mailto</a>),
155
+ nil
156
+ ]
157
+ )
158
+ end
159
+
160
+ it "recognizes and relative-ize absolute links outside the course but in one of the course's domains" do
161
+ test_string = %(<a href="https://apple.edu/courses/123">Mine</a><br><a href="https://kiwi.edu/courses/456">Vain</a><br><a href="http://other-canvas.example.com/">Other Instance</a>)
162
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<a href="/courses/123">Mine</a><br><a href="/courses/456">Vain</a><br><a href="http://other-canvas.example.com/">Other Instance</a>), nil])
163
+ end
164
+
165
+ it "prepends course files for unrecognized relative urls" do
166
+ test_string = %(<a href="/relative/path/to/file">Linkage</a>)
167
+ html, bad_links = @converter.convert_exported_html(test_string)
168
+ expect(html).to eq %(<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>)
169
+ expect(bad_links.length).to eq 1
170
+ expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/relative/path/to/file" })
171
+
172
+ test_string = %(<a href="relative/path/to/file">Linkage</a>)
173
+ html, bad_links = @converter.convert_exported_html(test_string)
174
+ expect(html).to eq %(<a href="#{@path}file_contents/course%20files/relative/path/to/file">Linkage</a>)
175
+ expect(bad_links.length).to eq 1
176
+ expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/relative/path/to/file" })
177
+
178
+ test_string = %(<a href="relative/path/to/file%20with%20space.html">Linkage</a>)
179
+ html, bad_links = @converter.convert_exported_html(test_string)
180
+ expect(html).to eq %(<a href="#{@path}file_contents/course%20files/relative/path/to/file%20with%20space.html">Linkage</a>)
181
+ expect(bad_links.length).to eq 1
182
+ expect(bad_links[0]).to include({ link_type: :file, missing_url: "/courses/2/file_contents/course%20files/relative/path/to/file%20with%20space.html" })
183
+ end
184
+
185
+ it "preserves media comment links" do
186
+ test_string = <<~HTML.strip
187
+ <p>
188
+ with media object url: <a id="media_comment_m-stuff" class="instructure_inline_media_comment video_comment" href="/media_objects/m-stuff">this is a media comment</a>
189
+ with file content url: <a id="media_comment_0_bq09qam2" class="instructure_inline_media_comment video_comment" href="/courses/2/file_contents/course%20files/media_objects/0_bq09qam2">this is a media comment</a>
190
+ </p>
191
+ HTML
192
+
193
+ expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
194
+ end
195
+
196
+ it "handles and repair half broken media links" do
197
+ test_string = %(<p><a href="/courses/2/file_contents/%24IMS_CC_FILEBASE%24/#" class="instructure_inline_media_comment video_comment" id="media_comment_m-stuff">this is a media comment</a><br><br></p>)
198
+
199
+ expect(@converter.convert_exported_html(test_string)).to eq([%(<p><a href="/media_objects/m-stuff" class="instructure_inline_media_comment video_comment" id="media_comment_m-stuff">this is a media comment</a><br><br></p>), nil])
200
+ end
201
+
202
+ it "preserves new RCE media iframes" do
203
+ test_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" src="/media_objects_iframe/m-stuff?type=video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"></iframe>)
204
+ expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
205
+ end
206
+
207
+ it "handles and repair half broken new RCE media iframes" do
208
+ test_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" src="%24IMS_CC_FILEBASE%24/#" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-abcde"></iframe>)
209
+ repaired_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" src="/media_objects_iframe/m-abcde?type=video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-abcde"></iframe>)
210
+ expect(@converter.convert_exported_html(test_string)).to eq([repaired_string, nil])
211
+ end
212
+
213
+ it "converts source tags to RCE media iframes" do
214
+ test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"><source src="/media_objects_iframe/m-stuff?type=video" data-media-id="m-stuff" data-media-type="video"></video>)
215
+ converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff" src="/media_objects_iframe/m-stuff?type=video"></iframe>)
216
+ expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
217
+
218
+ test_string = %(<audio style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff"><source src="/media_objects_iframe/m-stuff?type=audio" data-media-id="m-stuff" data-media-type="audio"></audio>)
219
+ converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff" src="/media_objects_iframe/m-stuff?type=audio"></iframe>)
220
+ expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
221
+ end
222
+
223
+ it "converts source tags to RCE media attachment iframes" do
224
+ test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"><source src="$CANVAS_OBJECT_REFERENCE$/media_attachments_iframe/E?type=video" data-media-id="m-stuff" data-media-type="video"></video>)
225
+ converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=video"></iframe>)
226
+ expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
227
+
228
+ test_string = %(<audio style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff"><source src="$CANVAS_OBJECT_REFERENCE$/media_attachments_iframe/E?type=audio" data-media-id="m-stuff" data-media-type="audio"></video>)
229
+ converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=audio"></iframe>)
230
+ expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
231
+ end
232
+
233
+ it "converts source tags to RCE media attachment iframes when link is untranslated" do
234
+ test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff"><source src="/media_attachments_iframe/5?type=video" data-media-id="m-stuff" data-media-type="video"></video>)
235
+ converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="video" allowfullscreen="allowfullscreen" allow="fullscreen" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=video"></iframe>)
236
+ expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
237
+
238
+ test_string = %(<audio style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff"><source src="/media_attachments_iframe/5?type=audio" data-media-id="m-stuff" data-media-type="audio"></video>)
239
+ converted_string = %(<iframe style="width: 400px; height: 225px; display: inline-block;" title="this is a media comment" data-media-type="audio" data-media-id="m-stuff" src="/media_attachments_iframe/5?type=audio"></iframe>)
240
+ expect(@converter.convert_exported_html(test_string)).to eq([converted_string, nil])
241
+ end
242
+
243
+ it "leaves source tags without data-media-id alone" do
244
+ test_string = %(<video style="width: 400px; height: 225px; display: inline-block;" title="this is a non-canvas video" allowfullscreen="allowfullscreen" allow="fullscreen"><source src="http://www.example.com/video.mov"></video>)
245
+ expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
246
+ end
247
+
248
+ it "only converts url params" do
249
+ test_string = <<~HTML
250
+ <object>
251
+ <param name="controls" value="CONSOLE" />
252
+ <param name="controller" value="true" />
253
+ <param name="autostart" value="false" />
254
+ <param name="loop" value="false" />
255
+ <param name="src" value="%24IMS_CC_FILEBASE%24/test.mp3" />
256
+ <EMBED name="tag" src="%24IMS_CC_FILEBASE%24/test.mp3" loop="false" autostart="false" controller="true" controls="CONSOLE" >
257
+ </EMBED>
258
+ </object>
259
+ HTML
260
+
261
+ expect(@converter.convert_exported_html(test_string)[0]).to match(<<~HTML.strip)
262
+ <object>
263
+ <param name="controls" value="CONSOLE">
264
+ <param name="controller" value="true">
265
+ <param name="autostart" value="false">
266
+ <param name="loop" value="false">
267
+ <param name="src" value="/courses/2/file_contents/course%20files/test.mp3">
268
+ <embed name="tag" src="/courses/2/file_contents/course%20files/test.mp3" loop="false" autostart="false" controller="true" controls="CONSOLE">
269
+
270
+ </object>
271
+ HTML
272
+ end
273
+
274
+ it "leaves an anchor tag alone" do
275
+ test_string = '<p><a href="#anchor_ref">ref</a></p>'
276
+ expect(@converter.convert_exported_html(test_string)).to eq([test_string, nil])
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (C) 2023 - present Instructure, Inc.
5
+ #
6
+ # This file is part of Canvas.
7
+ #
8
+ # Canvas is free software: you can redistribute it and/or modify it under
9
+ # the terms of the GNU Affero General Public License as published by the Free
10
+ # Software Foundation, version 3 of the License.
11
+ #
12
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ # details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License along
18
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require "spec_helper"
22
+ require "json"
23
+
24
+ describe CanvasLinkMigrator::LinkResolver do
25
+ def resolver(assets = JSON.parse(File.read("spec/fixtures/canvas_resource_map.json")))
26
+ CanvasLinkMigrator::LinkResolver.new(CanvasLinkMigrator::ResourceMapService.new(assets))
27
+ end
28
+
29
+ describe "resolve_link!" do
30
+ it "converts wiki_pages links" do
31
+ link = { link_type: :wiki_page, migration_id: "A", query: "?foo=bar" }
32
+ resolver.resolve_link!(link)
33
+ expect(link[:new_value]).to eq("/courses/2/pages/slug-a?foo=bar")
34
+ end
35
+
36
+ it "converts module_item links" do
37
+ link = { link_type: :module_item, migration_id: "C", query: "?foo=bar" }
38
+ resolver.resolve_link!(link)
39
+ expect(link[:new_value]).to eq("/courses/2/modules/items/3?foo=bar")
40
+ end
41
+
42
+ it "converts file_ref urls" do
43
+ link = { link_type: :file_ref, migration_id: "F" }
44
+ resolver.resolve_link!(link)
45
+ expect(link[:new_value]).to eq("/courses/2/files/6/preview")
46
+ end
47
+
48
+ it "converts attachment urls" do
49
+ link = { link_type: :object, type: "attachments", migration_id: "E", query: "?foo=bar" }
50
+ resolver.resolve_link!(link)
51
+ expect(link[:new_value]).to eq("/courses/2/files/5/preview")
52
+ end
53
+
54
+ it "converts media_attachments_iframe urls" do
55
+ link = { link_type: :object, type: "media_attachments_iframe", migration_id: "F", query: "?foo=bar" }
56
+ resolver.resolve_link!(link)
57
+ expect(link[:new_value]).to eq("/media_attachments_iframe/6?foo=bar")
58
+ end
59
+
60
+ it "converts discussion_topic links" do
61
+ link = { link_type: :discussion_topic, migration_id: "G", query: "?foo=bar" }
62
+ resolver.resolve_link!(link)
63
+ expect(link[:new_value]).to eq("/courses/2/discussion_topics/7?foo=bar")
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (C) 2012 - present Instructure, Inc.
5
+ #
6
+ # This file is part of Canvas.
7
+ #
8
+ # Canvas is free software: you can redistribute it and/or modify it under
9
+ # the terms of the GNU Affero General Public License as published by the Free
10
+ # Software Foundation, version 3 of the License.
11
+ #
12
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ # details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License along
18
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require "spec_helper"
22
+
23
+ describe CanvasLinkMigrator do
24
+ describe ".relative_url?" do
25
+ it "recognizes an absolute url" do
26
+ expect(CanvasLinkMigrator.relative_url?("http://example.com")).to be false
27
+ end
28
+
29
+ it "recognizes relative urls" do
30
+ expect(CanvasLinkMigrator.relative_url?("/relative/eh")).to be true
31
+ expect(CanvasLinkMigrator.relative_url?("also/relative")).to be true
32
+ expect(CanvasLinkMigrator.relative_url?("watup/nothing.html#anchoritbaby")).to be true
33
+ expect(CanvasLinkMigrator.relative_url?("watup/nothing?absolutely=1")).to be true
34
+ end
35
+
36
+ it "does not error on invalid urls" do
37
+ expect(CanvasLinkMigrator.relative_url?("stupid &^%$ url")).to be_falsey
38
+ expect(CanvasLinkMigrator.relative_url?("mailto:jfarnsworth@instructure.com,")).to be_falsey
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ {
2
+ "contains_migration_ids": true,
3
+ "destination_course": "2",
4
+ "destination_hosts": [
5
+ "apple.edu",
6
+ "kiwi.edu"
7
+ ],
8
+ "destination_root_folder": "course files/",
9
+ "resource_mapping": {
10
+ "discussion_topics": {
11
+ "G": {
12
+ "destination": {
13
+ "id": "7"
14
+ },
15
+ "source": {
16
+ "id": "6"
17
+ }
18
+ }
19
+ },
20
+ "files": {
21
+ "E": {
22
+ "destination": {
23
+ "id": "5",
24
+ "media_entry_id": "m-stuff"
25
+ },
26
+ "source": {
27
+ "id": "3",
28
+ "media_entry_id": "m-stuff"
29
+ }
30
+ },
31
+ "F": {
32
+ "destination": {
33
+ "id": "6",
34
+ "media_entry_id": "m-stuff"
35
+ },
36
+ "source": {
37
+ "id": "4",
38
+ "media_entry_id": "m-stuff"
39
+ }
40
+ }
41
+ },
42
+ "module_items": {
43
+ "C": {
44
+ "destination": {
45
+ "id": "3"
46
+ },
47
+ "source": {
48
+ "id": "2"
49
+ }
50
+ }
51
+ },
52
+ "wiki_pages": {
53
+ "A": {
54
+ "destination": {
55
+ "id": "2",
56
+ "url": "slug-a"
57
+ },
58
+ "source": {
59
+ "id": "1",
60
+ "url": "slug-a"
61
+ }
62
+ }
63
+ }
64
+ },
65
+ "file_tree": {
66
+ "folder 1": {
67
+ "id": "1",
68
+ "sub_folders": {}
69
+ }
70
+ },
71
+ "source_course": "1",
72
+ "source_host": "pineapple.edu"
73
+ }
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (C) 2021 - present Instructure, Inc.
5
+ #
6
+ # This file is part of Canvas.
7
+ #
8
+ # Canvas is free software: you can redistribute it and/or modify it under
9
+ # the terms of the GNU Affero General Public License as published by the Free
10
+ # Software Foundation, version 3 of the License.
11
+ #
12
+ # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ # details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License along
18
+ # with this program. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require "byebug"
21
+ require "canvas_link_migrator"
data/test.sh ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ set -e
3
+ bundle check || bundle install
4
+ bundle exec rspec spec