canvas_link_migrator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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