mod_organizer 1.0.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,115 @@
1
+ require 'fileutils'
2
+ require 'logger'
3
+ require 'mod_organizer'
4
+
5
+ module ModOrganizerTest
6
+
7
+ module Helpers
8
+
9
+ # ModOrganizer: The ModOrganizer instance that has been setup
10
+ attr_reader :mod_organizer
11
+
12
+ # String: The instance directory that has been setup
13
+ attr_reader :instance_dir
14
+
15
+ # Prepare a ModOrganizer instance directory
16
+ # The directory path is stored in an instance variable named @instance_dir.
17
+ #
18
+ # Parameters::
19
+ # * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the ini file to overwrite default values [default: {}]
20
+ # * *instance_name* (String or nil): The instance name to be used to mock a shared installation, or nil for a portable installation [default: nil]
21
+ def setup_instance_dir(ini: {}, instance_name: nil)
22
+ @instance_dir = "#{Dir.tmpdir}/ModOrganizerTest/ModOrganizer/#{instance_name || 'PortableInstance'}"
23
+ FileUtils.rm_rf(@instance_dir)
24
+ FileUtils.mkdir_p(@instance_dir)
25
+ IniFile.new(
26
+ content: {
27
+ General: {
28
+ gamePath: 'C:/path/to/game',
29
+ selected_profile: 'Default'
30
+ },
31
+ Settings: {
32
+ log_level: '1'
33
+ }
34
+ }.merge(ini) do |_section, default_properties, overwrite_properties|
35
+ default_properties.merge(overwrite_properties)
36
+ end
37
+ ).write(filename: "#{@instance_dir}/ModOrganizer.ini")
38
+ end
39
+
40
+ # Setup an instance of ModOrganizer setup on an instance directory.
41
+ # It uses the variable @instance_name to eventually setup a shared installation.
42
+ # The instance is stored in an instance variable named @mod_organizer.
43
+ #
44
+ # Parameters::
45
+ # * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the ini file to overwrite default values [default: {}]
46
+ def setup_mo(ini: {})
47
+ setup_instance_dir(ini:, instance_name: @instance_name)
48
+ mo_logger = StringIO.new
49
+ ENV['LOCALAPPDATA'] = "#{Dir.tmpdir}/ModOrganizerTest"
50
+ @mod_organizer = ModOrganizer.new(@instance_dir, instance_name: @instance_name, logger: Logger.new(mo_logger))
51
+ end
52
+
53
+ # Setup a mod in the default mods folder.
54
+ # Prerequisite: instance_dir has to be setup previously with setup_instance_dir.
55
+ #
56
+ # Parameters::
57
+ # * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the meta ini file to overwrite default values [default: {}]
58
+ # * *mod_name* (String): The mod name [default: 'TestMod']
59
+ # Result::
60
+ # * String: The mod directory
61
+ def setup_mod(ini: {}, mod_name: 'TestMod')
62
+ mod_dir = "#{instance_dir}/mods/#{mod_name}"
63
+ FileUtils.mkdir_p(mod_dir)
64
+ IniFile.new(
65
+ content: {
66
+ General: {
67
+ category: '2,',
68
+ url: 'https://test-mod.url',
69
+ installationFile: 'TestMod-v1.7z'
70
+ },
71
+ installedFiles: {
72
+ size: '1',
73
+ '1\\modid': '1337',
74
+ '1\\fileid': '666'
75
+ }
76
+ }.merge(ini) do |_section, default_properties, overwrite_properties|
77
+ default_properties.merge(overwrite_properties)
78
+ end
79
+ ).write(filename: "#{mod_dir}/meta.ini")
80
+ mod_dir
81
+ end
82
+
83
+ # Setup a download in the default downloads folder.
84
+ # Prerequisite: instance_dir has to be setup previously with setup_instance_dir.
85
+ #
86
+ # Parameters::
87
+ # * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the meta ini file to overwrite default values [default: {}]
88
+ # * *file_name* (String): The downloaded file name [default: 'TestMod-v1.7z']
89
+ # Result::
90
+ # * String: The downloads directory
91
+ def setup_download(ini: {}, file_name: 'TestMod-v1.7z')
92
+ downloads_dir = "#{instance_dir}/downloads"
93
+ FileUtils.mkdir_p(downloads_dir)
94
+ IniFile.new(
95
+ content: {
96
+ General: {
97
+ name: 'Test Mod file',
98
+ modID: '1107',
99
+ fileID: '42'
100
+ }
101
+ }.merge(ini) do |_section, default_properties, overwrite_properties|
102
+ default_properties.merge(overwrite_properties)
103
+ end
104
+ ).write(filename: "#{downloads_dir}/#{file_name}.meta")
105
+ File.write("#{downloads_dir}/#{file_name}", "#{file_name} downloaded content")
106
+ downloads_dir
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+
113
+ RSpec.configure do |config|
114
+ config.include ModOrganizerTest::Helpers
115
+ end
@@ -0,0 +1,55 @@
1
+ require 'time'
2
+
3
+ describe ModOrganizer::Download do
4
+
5
+ let(:download) { mod_organizer.mod(name: 'TestMod').sources.first.download }
6
+ let(:download_dir) { "#{instance_dir}/downloads" }
7
+ let(:downloaded_file) { "#{instance_dir}/downloads/TestMod-v1.7z" }
8
+
9
+ before do
10
+ setup_mo
11
+ setup_mod
12
+ setup_download
13
+ end
14
+
15
+ it 'returns the downloaded file path' do
16
+ expect(download.downloaded_file_path).to eq downloaded_file
17
+ end
18
+
19
+ it 'returns the downloaded file date' do
20
+ file_date = Time.parse('2023-01-05 10:20:30 UTC')
21
+ File.utime(File.atime(downloaded_file), file_date, downloaded_file)
22
+ expect(download.downloaded_date).to eq file_date
23
+ end
24
+
25
+ it 'returns the NexusMods file name' do
26
+ expect(download.nexus_file_name).to eq 'Test Mod file'
27
+ end
28
+
29
+ it 'caches the returned NexusMods file name' do
30
+ expect(download.nexus_file_name).to eq 'Test Mod file'
31
+ setup_download(ini: { General: { name: 'Alternative Test Mod file' } })
32
+ expect(download.nexus_file_name).to eq 'Test Mod file'
33
+ end
34
+
35
+ it 'returns the NexusMods mod id' do
36
+ expect(download.nexus_mod_id).to eq 1107
37
+ end
38
+
39
+ it 'caches the returned NexusMods mod id' do
40
+ expect(download.nexus_mod_id).to eq 1107
41
+ setup_download(ini: { General: { modID: '666' } })
42
+ expect(download.nexus_mod_id).to eq 1107
43
+ end
44
+
45
+ it 'returns the NexusMods file id' do
46
+ expect(download.nexus_file_id).to eq 42
47
+ end
48
+
49
+ it 'caches the returned NexusMods file id' do
50
+ expect(download.nexus_file_id).to eq 42
51
+ setup_download(ini: { General: { fileID: '666' } })
52
+ expect(download.nexus_file_id).to eq 42
53
+ end
54
+
55
+ end
@@ -0,0 +1,184 @@
1
+ describe ModOrganizer::Mod do
2
+
3
+ let(:mod) { mod_organizer.mod(name: 'TestMod') }
4
+ let(:mod_dir) { "#{instance_dir}/mods/TestMod" }
5
+
6
+ context 'with a simple mod' do
7
+
8
+ before do
9
+ setup_mo
10
+ setup_mod
11
+ end
12
+
13
+ it 'returns the mod name' do
14
+ expect(mod.name).to eq 'TestMod'
15
+ end
16
+
17
+ it 'returns the mod enabled flag when enabled' do
18
+ FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
19
+ File.write(
20
+ "#{instance_dir}/profiles/Default/modlist.txt",
21
+ <<~EO_MOD_LIST
22
+ +TestMod
23
+ EO_MOD_LIST
24
+ )
25
+ expect(mod.enabled?).to be(true)
26
+ end
27
+
28
+ it 'returns the mod enabled flag when disabled' do
29
+ FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
30
+ File.write(
31
+ "#{instance_dir}/profiles/Default/modlist.txt",
32
+ <<~EO_MOD_LIST
33
+ -TestMod
34
+ EO_MOD_LIST
35
+ )
36
+ expect(mod.enabled?).to be(false)
37
+ end
38
+
39
+ it 'returns the mod categories' do
40
+ expect(mod.categories).to eq %w[Armour]
41
+ end
42
+
43
+ it 'returns no plugins for mods having no plugins' do
44
+ expect(mod.plugins).to eq []
45
+ end
46
+
47
+ it 'returns plugins from the mod\'s root directory' do
48
+ File.write("#{mod_dir}/plugin1.esm", 'Plugin1 esm content')
49
+ File.write("#{mod_dir}/plugin2.esp", 'Plugin2 esp content')
50
+ File.write("#{mod_dir}/plugin3.esl", 'Plugin3 esl content')
51
+ File.write("#{mod_dir}/other_file.txt", 'Other file content')
52
+ FileUtils.mkdir_p("#{mod_dir}/others")
53
+ File.write("#{mod_dir}/others/plugin4.esp", 'Plugin4 esp content')
54
+ expect(mod.plugins.sort).to eq %w[
55
+ plugin1.esm
56
+ plugin2.esp
57
+ plugin3.esl
58
+ ]
59
+ end
60
+
61
+ it 'returns plugins in lower case' do
62
+ File.write("#{mod_dir}/PlUgIn1.esm", 'Plugin1 esm content')
63
+ expect(mod.plugins).to eq %w[plugin1.esm]
64
+ end
65
+
66
+ it 'caches plugins being returned' do
67
+ File.write("#{mod_dir}/plugin1.esm", 'Plugin1 esm content')
68
+ expect(mod.plugins).to eq %w[plugin1.esm]
69
+ File.unlink("#{mod_dir}/plugin1.esm")
70
+ expect(mod.plugins).to eq %w[plugin1.esm]
71
+ end
72
+
73
+ it 'returns the mod source' do
74
+ sources = mod.sources
75
+ expect(sources.size).to be 1
76
+ expect(sources.first.type).to be :nexus_mods
77
+ expect(sources.first.nexus_mod_id).to be 1337
78
+ expect(sources.first.nexus_file_id).to be 666
79
+ expect(sources.first.file_name).to eq 'TestMod-v1.7z'
80
+ end
81
+
82
+ it 'caches the returned the mod source' do
83
+ expect(mod.sources.first.nexus_mod_id).to be 1337
84
+ setup_mod(ini: { installedFiles: { '1\\modid': '1107' } })
85
+ expect(mod.sources.first.nexus_mod_id).to be 1337
86
+ end
87
+
88
+ it 'returns the mod URL' do
89
+ expect(mod.url).to eq 'https://test-mod.url'
90
+ end
91
+
92
+ end
93
+
94
+ context 'with a mod having no ini file' do
95
+
96
+ before do
97
+ setup_mo
98
+ setup_mod
99
+ File.unlink("#{mod_dir}/meta.ini")
100
+ end
101
+
102
+ it 'returns the mod name' do
103
+ expect(mod.name).to eq 'TestMod'
104
+ end
105
+
106
+ it 'returns the mod enabled flag when enabled' do
107
+ FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
108
+ File.write(
109
+ "#{instance_dir}/profiles/Default/modlist.txt",
110
+ <<~EO_MOD_LIST
111
+ +TestMod
112
+ EO_MOD_LIST
113
+ )
114
+ expect(mod.enabled?).to be(true)
115
+ end
116
+
117
+ it 'returns the mod enabled flag when disabled' do
118
+ FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
119
+ File.write(
120
+ "#{instance_dir}/profiles/Default/modlist.txt",
121
+ <<~EO_MOD_LIST
122
+ -TestMod
123
+ EO_MOD_LIST
124
+ )
125
+ expect(mod.enabled?).to be(false)
126
+ end
127
+
128
+ it 'returns no mod categories' do
129
+ expect(mod.categories).to eq []
130
+ end
131
+
132
+ it 'returns an unknown mod source' do
133
+ sources = mod.sources
134
+ expect(sources.size).to be 1
135
+ expect(sources.first.type).to be :unknown
136
+ expect(sources.first.nexus_mod_id).to be_nil
137
+ expect(sources.first.nexus_file_id).to be_nil
138
+ expect(sources.first.file_name).to be_nil
139
+ end
140
+
141
+ it 'returns no mod URL' do
142
+ expect(mod.url).to be_nil
143
+ end
144
+
145
+ end
146
+
147
+ context 'with a mod having several sources' do
148
+
149
+ before do
150
+ setup_mo
151
+ setup_mod(
152
+ ini: {
153
+ installedFiles: {
154
+ size: '3',
155
+ '1\\modid': '100',
156
+ '1\\fileid': '200',
157
+ '2\\modid': '101',
158
+ '2\\fileid': '201',
159
+ '3\\modid': '102',
160
+ '3\\fileid': '202'
161
+ }
162
+ }
163
+ )
164
+ end
165
+
166
+ it 'returns several sources' do
167
+ expect(mod.sources.map.with_index { |source, idx| [idx, source.nexus_mod_id, source.nexus_file_id] }.sort).to eq [
168
+ [0, 100, 200],
169
+ [1, 101, 201],
170
+ [2, 102, 202]
171
+ ]
172
+ end
173
+
174
+ it 'associates the downloaded file name to the last source only' do
175
+ expect(mod.sources.map.with_index { |source, idx| [idx, source.file_name] }.sort).to eq [
176
+ [0, nil],
177
+ [1, nil],
178
+ [2, 'TestMod-v1.7z']
179
+ ]
180
+ end
181
+
182
+ end
183
+
184
+ end
@@ -0,0 +1,71 @@
1
+ describe ModOrganizer::Source do
2
+
3
+ let(:source) { mod_organizer.mod(name: 'TestMod').sources.first }
4
+
5
+ context 'with a source from nexus_mods' do
6
+
7
+ before do
8
+ setup_mo
9
+ setup_mod
10
+ end
11
+
12
+ it 'returns the NexusMods mod id' do
13
+ expect(source.nexus_mod_id).to be 1337
14
+ end
15
+
16
+ it 'returns the NexusMods file id' do
17
+ expect(source.nexus_file_id).to be 666
18
+ end
19
+
20
+ it 'returns the source file name' do
21
+ expect(source.file_name).to eq 'TestMod-v1.7z'
22
+ end
23
+
24
+ it 'returns the source type' do
25
+ expect(source.type).to be :nexus_mods
26
+ end
27
+
28
+ it 'returns the download info of the source' do
29
+ FileUtils.mkdir_p("#{instance_dir}/downloads")
30
+ downloaded_file = "#{instance_dir}/downloads/TestMod-v1.7z"
31
+ File.write(downloaded_file, 'TestMod v1 downloaded content')
32
+ expect(source.download.downloaded_file_path).to eq downloaded_file
33
+ end
34
+
35
+ it 'returns no download info of the source if the downloaded file is missing' do
36
+ expect(source.download).to be_nil
37
+ end
38
+
39
+ end
40
+
41
+ context 'with an unknown source' do
42
+
43
+ before do
44
+ setup_mo
45
+ setup_mod
46
+ File.unlink("#{instance_dir}/mods/TestMod/meta.ini")
47
+ end
48
+
49
+ it 'returns no NexusMods mod id' do
50
+ expect(source.nexus_mod_id).to be_nil
51
+ end
52
+
53
+ it 'returns no NexusMods file id' do
54
+ expect(source.nexus_file_id).to be_nil
55
+ end
56
+
57
+ it 'returns no source file name' do
58
+ expect(source.file_name).to be_nil
59
+ end
60
+
61
+ it 'returns the source type' do
62
+ expect(source.type).to be :unknown
63
+ end
64
+
65
+ it 'returns no download info of the source' do
66
+ expect(source.download).to be_nil
67
+ end
68
+
69
+ end
70
+
71
+ end