mtg-playerinfo 1.2.2 → 1.3.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,104 @@
1
+ [
2
+ {
3
+ "short_id": "ZcCUi2BJ7g5ubNrREqKS3C",
4
+ "match_start": 1770848945000,
5
+ "season_ordinal": 86,
6
+ "event_name": "Traditional_Ladder",
7
+ "super_format": 2,
8
+ "match_win_condition": 2,
9
+ "games": [
10
+ {
11
+ "game_number": 1,
12
+ "game_duration_seconds": 587,
13
+ "active_player_id": 1,
14
+ "winning_team_id": 2,
15
+ "friendly_deckstring": "AAQAAQqMBQHz1C2kAxkD1vkHqPQChuwHlrgBBbzrAd_rM7j6ApWaCeEEBIdk4JwsCofsFAe16wHuUMuQL5sEr50Qf5k6AAIJ_uIC6_EW1qsTosgL4-4HgQSjsAF2_AIDlxCMwzWO1QwAAAAA",
16
+ "player_opening_hands": [
17
+ "AAQAAQe16wEH51DOxCrMXNn5B736AgAAAAAA"
18
+ ],
19
+ "player_mulligan_put_on_bottom": null,
20
+ "opponent_revealed_colors": [
21
+ 11
22
+ ],
23
+ "opponent_revealed_archetype": {
24
+ "AAQAAQmMBaINvlHE7gbaiSagAZw317sKwNsIB64a_F7QSqKZLL_1B7UE9N8KA6v-LNvSBJP7BgAAAA": null
25
+ },
26
+ "opponent_revealed_deckstrings": [
27
+ "AAQAAQmMBaINvlHE7gbaiSagAZw317sKwNsIB64a_F7QSqKZLL_1B7UE9N8KA6v-LNvSBJP7BgAAAA"
28
+ ]
29
+ },
30
+ {
31
+ "game_number": 2,
32
+ "game_duration_seconds": 335,
33
+ "active_player_id": 1,
34
+ "winning_team_id": 1,
35
+ "friendly_deckstring": "AAQAAQyMBQHx3QLBnSrBWaQDGQPW-QfL8QLdAqChCQeXEKXbAd_rM7j6AvHlB4W5Aeg3Aodk6pwsB7XrAe5Qy5AvmwSvnRB_mToAAgLp1Bnc5iYCo9M1t5wMA-eALYHrFJABAAAA",
36
+ "player_opening_hands": [
37
+ "AAQAAQWXEJ7bAYqVK6_MBPP7BgGjvAIAAAAA"
38
+ ],
39
+ "player_mulligan_put_on_bottom": null,
40
+ "opponent_revealed_colors": [
41
+ 11
42
+ ],
43
+ "opponent_revealed_archetype": {
44
+ "AAQAAQqMBaIV_F7QSpCYLKABsfUHrgSQ9QK_AQGQ1zUAAAAA": null
45
+ },
46
+ "opponent_revealed_deckstrings": [
47
+ "AAQAAQqMBaIV_F7QSpCYLKABsfUHrgSQ9QK_AQGQ1zUAAAAA"
48
+ ]
49
+ },
50
+ {
51
+ "game_number": 3,
52
+ "game_duration_seconds": 190,
53
+ "active_player_id": 2,
54
+ "winning_team_id": 1,
55
+ "friendly_deckstring": "AAQAAQyMBQHx3QLBnSrBWaQDGQPW-QfL8QLdAqChCQeXEKXbAd_rM7j6AvHlB4W5Aeg3Aodk6pwsB7XrAe5Qy5AvmwSvnRB_mToAAgLp1Bnc5iYCo9M1t5wMA-eALYHrFJABAAAA",
56
+ "player_opening_hands": [
57
+ "AAQAAQWHZJzYAc7EKqrWCJbRDAG370EAAAAA"
58
+ ],
59
+ "player_mulligan_put_on_bottom": null,
60
+ "opponent_revealed_colors": [
61
+ 3
62
+ ],
63
+ "opponent_revealed_archetype": {
64
+ "AAQAAQeuEoAIvkm-FZBy1Oor-4AIAYS3QAAAAAA": null
65
+ },
66
+ "opponent_revealed_deckstrings": [
67
+ "AAQAAQeuEoAIvkm-FZBy1Oor-4AIAYS3QAAAAAA"
68
+ ]
69
+ }
70
+ ],
71
+ "winning_team_id": 1,
72
+ "active_player_id": 1,
73
+ "friendly_deckstring": "AAQAAQqMBQHz1C2kAxkD1vkHqPQChuwHlrgBBbzrAd_rM7j6ApWaCeEEBIdk4JwsCofsFAe16wHuUMuQL5sEr50Qf5k6AAIJ_uIC6_EW1qsTosgL4-4HgQSjsAF2_AIDlxCMwzWO1QwAAAAA",
74
+ "friendly_deck_name": "Sultai Combo v2",
75
+ "friendly_deck_id": "5e2004f0-e55c-4081-8284-22ca03db47c8",
76
+ "friendly_deck_colors": 22,
77
+ "friendly_deck_tile_id": 98497,
78
+ "friendly_deck_archetype": null,
79
+ "friendly_system_seat_id": 1,
80
+ "friendly_team_id": 1,
81
+ "friendly_ranking_class_before": "Platinum",
82
+ "friendly_ranking_tier_before": 4,
83
+ "friendly_ranking_tier_steps_before": 2,
84
+ "friendly_mythic_percentile_before": 0.0,
85
+ "friendly_mythic_leaderboard_place_before": 0,
86
+ "friendly_rating_before": null,
87
+ "friendly_rating_deviation_before": null,
88
+ "friendly_rating_volatility_before": null,
89
+ "friendly_rating_mythic_before": null,
90
+ "friendly_ranking_class_after": "Platinum",
91
+ "friendly_ranking_tier_after": 4,
92
+ "friendly_ranking_tier_steps_after": 4,
93
+ "friendly_mythic_percentile_after": 0.0,
94
+ "friendly_mythic_leaderboard_place_after": 0,
95
+ "friendly_course_wins_before": null,
96
+ "friendly_course_losses_before": null,
97
+ "opponents": [
98
+ {
99
+ "player_name": "MysticalMachineGun",
100
+ "team_id": 2
101
+ }
102
+ ]
103
+ }
104
+ ]
@@ -0,0 +1,128 @@
1
+ const test = require('node:test')
2
+ const assert = require('node:assert/strict')
3
+ const PlayerInfoManager = require('../src')
4
+
5
+ test('PlayerInfoManager: getPlayerInfo handles all null results gracefully', async () => {
6
+ const manager = new PlayerInfoManager()
7
+
8
+ manager.fetchers.unity.fetchById = async () => null
9
+ manager.fetchers.mtgelo.fetchById = async () => null
10
+ manager.fetchers.melee.fetchById = async () => null
11
+ manager.fetchers.topdeck.fetchById = async () => null
12
+
13
+ const result = await manager.getPlayerInfo({
14
+ unityId: '123',
15
+ mtgeloId: '456',
16
+ meleeUser: 'test',
17
+ topdeckHandle: 'test'
18
+ })
19
+
20
+ assert.deepEqual(result.general, {}, 'General should be empty object')
21
+ assert.deepEqual(result.sources, {}, 'Sources should be empty object')
22
+ assert.ok(!result.general['win rate'], 'Should not have win rate with no valid results')
23
+ })
24
+
25
+ test('PlayerInfoManager: getPlayerInfo handles partial null results', async () => {
26
+ const manager = new PlayerInfoManager()
27
+
28
+ manager.fetchers.unity.fetchById = async () => ({
29
+ source: 'Unity League',
30
+ url: 'http://unity.test',
31
+ name: 'Test Player'
32
+ })
33
+ manager.fetchers.mtgelo.fetchById = async () => null
34
+ manager.fetchers.melee.fetchById = async () => null
35
+ manager.fetchers.topdeck.fetchById = async () => null
36
+
37
+ const result = await manager.getPlayerInfo({
38
+ unityId: '123',
39
+ mtgeloId: '456',
40
+ meleeUser: 'test',
41
+ topdeckHandle: 'test'
42
+ })
43
+
44
+ assert.equal(result.general.name, 'Test Player', 'Should have data from Unity League')
45
+ assert.equal(Object.keys(result.sources).length, 1, 'Should have only one source')
46
+ assert.ok(result.sources['Unity League'], 'Should have Unity League in sources')
47
+ })
48
+
49
+ test('PlayerInfoManager: mergeData handles records without draws (W-L format)', async () => {
50
+ const manager = new PlayerInfoManager()
51
+
52
+ const results = [
53
+ {
54
+ source: 'Test Source',
55
+ url: 'http://test.com',
56
+ name: 'Test Player',
57
+ record: '10-5'
58
+ }
59
+ ]
60
+
61
+ const merged = manager.mergeData(results, false)
62
+
63
+ assert.equal(merged.general['win rate'], '66.67%', 'Should calculate win rate correctly for W-L format')
64
+ })
65
+
66
+ test('PlayerInfoManager: mergeData handles invalid win rate strings gracefully', async () => {
67
+ const manager = new PlayerInfoManager()
68
+
69
+ const results = [
70
+ {
71
+ source: 'Test1',
72
+ url: 'http://test1.com',
73
+ name: 'Test',
74
+ winRate: 'invalid%'
75
+ },
76
+ {
77
+ source: 'Test2',
78
+ url: 'http://test2.com',
79
+ name: 'Test',
80
+ 'win rate': 'N/A'
81
+ },
82
+ {
83
+ source: 'Test3',
84
+ url: 'http://test3.com',
85
+ name: 'Test',
86
+ record: '10-5-0'
87
+ }
88
+ ]
89
+
90
+ const merged = manager.mergeData(results, false)
91
+
92
+ // Should not crash, should calculate from record only
93
+ assert.ok(merged, 'Should not crash on invalid win rate strings')
94
+ assert.equal(merged.general['win rate'], '66.67%', 'Should calculate from valid record')
95
+ })
96
+
97
+ test('PlayerInfoManager: mergeData returns no win rate when insufficient data', async () => {
98
+ const manager = new PlayerInfoManager()
99
+
100
+ const results = [
101
+ {
102
+ source: 'Test',
103
+ url: 'http://test.com',
104
+ name: 'Test Player'
105
+ }
106
+ ]
107
+
108
+ const merged = manager.mergeData(results, false)
109
+
110
+ assert.ok(!merged.general['win rate'], 'Should not calculate win rate without data')
111
+ })
112
+
113
+ test('PlayerInfoManager: mergeData handles record with all zeros', async () => {
114
+ const manager = new PlayerInfoManager()
115
+
116
+ const results = [
117
+ {
118
+ source: 'Test',
119
+ url: 'http://test.com',
120
+ name: 'Test Player',
121
+ record: '0-0-0'
122
+ }
123
+ ]
124
+
125
+ const merged = manager.mergeData(results, false)
126
+
127
+ assert.ok(!merged.general['win rate'], 'Should not calculate win rate when no games played')
128
+ })
@@ -0,0 +1,53 @@
1
+ const test = require('node:test')
2
+ const assert = require('node:assert/strict')
3
+ const MeleeFetcher = require('../src/fetchers/melee')
4
+
5
+ test('MeleeFetcher: parseHtml uses username when name not found in HTML', () => {
6
+ const fetcher = new MeleeFetcher()
7
+ const html = '<html><body></body></html>'
8
+
9
+ const result = fetcher.parseHtml(html, 'http://test.com', 'fallbackname')
10
+
11
+ assert.equal(result.name, 'fallbackname', 'Should use username as fallback')
12
+ assert.equal(result.source, 'Melee')
13
+ assert.equal(result.url, 'http://test.com')
14
+ })
15
+
16
+ test('MeleeFetcher: parseHtml handles invalid social link URLs gracefully', () => {
17
+ const fetcher = new MeleeFetcher()
18
+ const html = `
19
+ <html>
20
+ <body>
21
+ <span style="font-size: xx-large">Test Name</span>
22
+ <a class="social-link" href="not-a-valid-url">Invalid</a>
23
+ <a class="social-link" href="https://twitter.com/validuser">Valid</a>
24
+ </body>
25
+ </html>
26
+ `
27
+
28
+ const result = fetcher.parseHtml(html, 'http://test.com', 'testuser')
29
+
30
+ assert.equal(result.name, 'Test Name')
31
+ assert.equal(result.twitter, 'validuser', 'Should parse valid URL')
32
+ // Should not crash on invalid URL
33
+ })
34
+
35
+ test('MeleeFetcher: parseHtml extracts multiple social links correctly', () => {
36
+ const fetcher = new MeleeFetcher()
37
+ const html = `
38
+ <html>
39
+ <body>
40
+ <span style="font-size: xx-large">Test Player</span>
41
+ <a class="social-link" href="https://twitter.com/testtwitter">Twitter</a>
42
+ <a class="social-link" href="https://www.facebook.com/testfacebook">Facebook</a>
43
+ <a class="social-link" href="https://twitch.tv/testtwitch">Twitch</a>
44
+ </body>
45
+ </html>
46
+ `
47
+
48
+ const result = fetcher.parseHtml(html, 'http://test.com', 'testuser')
49
+
50
+ assert.equal(result.twitter, 'testtwitter')
51
+ assert.equal(result.facebook, 'testfacebook')
52
+ assert.equal(result.twitch, 'testtwitch')
53
+ })
@@ -0,0 +1,92 @@
1
+ const test = require('node:test')
2
+ const assert = require('node:assert/strict')
3
+ const MtgEloFetcher = require('../src/fetchers/mtgElo')
4
+
5
+ test('MtgEloFetcher: parseHtml returns null when no name found', () => {
6
+ const fetcher = new MtgEloFetcher()
7
+ const html = '<html><body></body></html>'
8
+
9
+ const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
10
+
11
+ assert.equal(result, null, 'Should return null when name cannot be extracted')
12
+ })
13
+
14
+ test('MtgEloFetcher: parseHtml handles invalid JSON in astro-island gracefully', () => {
15
+ const fetcher = new MtgEloFetcher()
16
+ const html = `
17
+ <html>
18
+ <astro-island component-url="Profile" props='invalid json'></astro-island>
19
+ <div class="text-[22pt]">Fallback, Name</div>
20
+ <div class="text-[18pt]">Current rating: <span class="font-bold">1500</span></div>
21
+ <div class="text-[18pt]">Record: 10-5-2</div>
22
+ </html>
23
+ `
24
+
25
+ const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
26
+
27
+ assert.equal(result.name, 'Name Fallback', 'Should use fallback parsing with flipped name')
28
+ assert.equal(result.current_rating, '1500')
29
+ assert.equal(result.record, '10-5-2')
30
+ })
31
+
32
+ test('MtgEloFetcher: parseHtml uses fallback selectors when astro-island missing', () => {
33
+ const fetcher = new MtgEloFetcher()
34
+ const html = `
35
+ <html>
36
+ <div class="text-[22pt]">Smith, John</div>
37
+ <div class="text-[18pt]">Current rating: <span class="font-bold">1600</span></div>
38
+ <div class="text-[18pt]">Record: 20-10-5</div>
39
+ </html>
40
+ `
41
+
42
+ const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
43
+
44
+ assert.equal(result.name, 'John Smith', 'Should flip comma-separated name')
45
+ assert.equal(result.current_rating, '1600')
46
+ assert.equal(result.record, '20-10-5')
47
+ assert.equal(result['win rate'], '57.14%', 'Should calculate win rate from record')
48
+ })
49
+
50
+ test('MtgEloFetcher: parseHtml calculates win rate correctly with draws', () => {
51
+ const fetcher = new MtgEloFetcher()
52
+ const html = `
53
+ <html>
54
+ <div class="text-[22pt]">Test Player</div>
55
+ <div class="text-[18pt]">Record: 15-10-5</div>
56
+ </html>
57
+ `
58
+
59
+ const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
60
+
61
+ assert.equal(result.record, '15-10-5')
62
+ assert.equal(result['win rate'], '50.00%')
63
+ })
64
+
65
+ test('MtgEloFetcher: parseHtml handles record without draws', () => {
66
+ const fetcher = new MtgEloFetcher()
67
+ const html = `
68
+ <html>
69
+ <div class="text-[22pt]">Test Player</div>
70
+ <div class="text-[18pt]">Record: 12-8</div>
71
+ </html>
72
+ `
73
+
74
+ const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
75
+
76
+ assert.equal(result.record, '12-8')
77
+ assert.equal(result['win rate'], '60.00%')
78
+ })
79
+
80
+ test('MtgEloFetcher: parseHtml handles name without comma', () => {
81
+ const fetcher = new MtgEloFetcher()
82
+ const html = `
83
+ <html>
84
+ <div class="text-[22pt]">SingleName</div>
85
+ <div class="text-[18pt]">Record: 5-5-0</div>
86
+ </html>
87
+ `
88
+
89
+ const result = fetcher.parseHtml(html, 'http://test.com', 'test-id')
90
+
91
+ assert.equal(result.name, 'SingleName', 'Should handle single name without comma')
92
+ })
@@ -3,10 +3,10 @@ const assert = require('node:assert/strict')
3
3
  const fs = require('node:fs')
4
4
  const path = require('node:path')
5
5
 
6
- const PlayerInfoManager = require('../../src')
6
+ const PlayerInfoManager = require('../src')
7
7
 
8
8
  function readFixture (name) {
9
- return fs.readFileSync(path.join(__dirname, '..', 'data', name), 'utf8')
9
+ return fs.readFileSync(path.join(__dirname, 'data', name), 'utf8')
10
10
  }
11
11
 
12
12
  const fixtures = {
@@ -0,0 +1,123 @@
1
+ const test = require('node:test')
2
+ const assert = require('node:assert/strict')
3
+ const UnityLeagueFetcher = require('../src/fetchers/unityLeague')
4
+
5
+ test('UnityLeagueFetcher: parseHtml ignores non-player_profile images', () => {
6
+ const fetcher = new UnityLeagueFetcher()
7
+ const html = `
8
+ <html>
9
+ <h1 class="d-inline">Test Player</h1>
10
+ <div class="card-body">
11
+ <img class="img-fluid" src="https://unityleague.gg/media/default_avatar.jpg" />
12
+ </div>
13
+ </html>
14
+ `
15
+
16
+ const result = fetcher.parseHtml(html, 'http://test.com')
17
+
18
+ assert.equal(result.photo, null, 'Should not use non-player_profile images')
19
+ assert.equal(result.name, 'Test Player')
20
+ })
21
+
22
+ test('UnityLeagueFetcher: parseHtml handles missing country data gracefully', () => {
23
+ const fetcher = new UnityLeagueFetcher()
24
+ const html = `
25
+ <html>
26
+ <h1 class="d-inline">Test Player</h1>
27
+ <dt class="small text-muted">Country:</dt>
28
+ <dd></dd>
29
+ </html>
30
+ `
31
+
32
+ const result = fetcher.parseHtml(html, 'http://test.com')
33
+
34
+ assert.ok(result, 'Should return result even with missing country')
35
+ assert.equal(result.name, 'Test Player')
36
+ assert.equal(result.country, '', 'Country should be empty string')
37
+ })
38
+
39
+ test('UnityLeagueFetcher: parseHtml handles country text without flag icon', () => {
40
+ const fetcher = new UnityLeagueFetcher()
41
+ const html = `
42
+ <html>
43
+ <h1 class="d-inline">Test Player</h1>
44
+ <dt class="small text-muted">Country:</dt>
45
+ <dd>Germany</dd>
46
+ </html>
47
+ `
48
+
49
+ const result = fetcher.parseHtml(html, 'http://test.com')
50
+
51
+ assert.equal(result.country, 'Germany', 'Should use text when no flag icon')
52
+ })
53
+
54
+ test('UnityLeagueFetcher: parseHtml handles missing bio element', () => {
55
+ const fetcher = new UnityLeagueFetcher()
56
+ const html = `
57
+ <html>
58
+ <h1 class="d-inline">Test Player</h1>
59
+ </html>
60
+ `
61
+
62
+ const result = fetcher.parseHtml(html, 'http://test.com')
63
+
64
+ assert.ok(!result.bio, 'Bio should be undefined when element missing')
65
+ })
66
+
67
+ test('UnityLeagueFetcher: parseHtml extracts country from header flag', () => {
68
+ const fetcher = new UnityLeagueFetcher()
69
+ const html = `
70
+ <html>
71
+ <h1 class="d-inline">Test Player</h1>
72
+ <div class="card-body">
73
+ <i class="fi fi-us"></i>
74
+ </div>
75
+ </html>
76
+ `
77
+
78
+ const result = fetcher.parseHtml(html, 'http://test.com')
79
+
80
+ assert.equal(result.country, 'us', 'Should extract country code from header flag')
81
+ })
82
+
83
+ test('UnityLeagueFetcher: parseHtml handles missing ranking table', () => {
84
+ const fetcher = new UnityLeagueFetcher()
85
+ const html = `
86
+ <html>
87
+ <h1 class="d-inline">Test Player</h1>
88
+ </html>
89
+ `
90
+
91
+ const result = fetcher.parseHtml(html, 'http://test.com')
92
+
93
+ assert.ok(result, 'Should handle missing ranking table')
94
+ assert.ok(!result['rank germany'], 'Should not have rank data')
95
+ })
96
+
97
+ test('UnityLeagueFetcher: parseHtml extracts ranking data with special characters', () => {
98
+ const fetcher = new UnityLeagueFetcher()
99
+ const html = `
100
+ <html>
101
+ <h1 class="d-inline">Test Player</h1>
102
+ <table class="table-sm">
103
+ <thead>
104
+ <tr>
105
+ <th>Germany</th>
106
+ <th>Europe</th>
107
+ </tr>
108
+ </thead>
109
+ <tbody>
110
+ <tr>
111
+ <td>#42</td>
112
+ <td>1st</td>
113
+ </tr>
114
+ </tbody>
115
+ </table>
116
+ </html>
117
+ `
118
+
119
+ const result = fetcher.parseHtml(html, 'http://test.com')
120
+
121
+ assert.equal(result['rank germany'], '42', 'Should extract numeric value from #42')
122
+ assert.equal(result['rank europe'], '1', 'Should extract numeric value from 1st')
123
+ })
@@ -0,0 +1,58 @@
1
+ const test = require('node:test')
2
+ const assert = require('node:assert/strict')
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ const UntappedFetcher = require('../src/fetchers/untapped')
7
+
8
+ function readFixture (name) {
9
+ return fs.readFileSync(path.join(__dirname, 'data', name), 'utf8')
10
+ }
11
+
12
+ test('UntappedFetcher: parses most recent match and extracts MTGA rank', () => {
13
+ const fetcher = new UntappedFetcher()
14
+ const fixtureJson = readFixture('untapped.json')
15
+ const matches = JSON.parse(fixtureJson)
16
+
17
+ const url = 'https://mtga.untapped.gg/profile/7de50700-c3f6-48e4-a38d-2add5b0d9b71/76DCDWCZS5FX5PIEEMUVY6GV74'
18
+ const result = fetcher.parseMatch(matches[0], url)
19
+
20
+ assert.strictEqual(result.source, 'Untapped.gg')
21
+ assert.strictEqual(result.url, url)
22
+
23
+ assert.match(result.mtga_rank, /^(Bronze|Silver|Gold|Platinum|Diamond|Mythic)\s\d+$/)
24
+ })
25
+
26
+ test('UntappedFetcher: handles missing rank data', () => {
27
+ const fetcher = new UntappedFetcher()
28
+ const testMatch = {
29
+ friendly_ranking_class_after: null,
30
+ friendly_ranking_tier_after: null
31
+ }
32
+
33
+ const url = 'https://mtga.untapped.gg/profile/test-user/test-code'
34
+ const result = fetcher.parseMatch(testMatch, url)
35
+
36
+ assert.strictEqual(result.mtga_rank, null)
37
+ })
38
+
39
+ test('UntappedFetcher: constructs correct API URL from two-part ID', () => {
40
+ const fetcher = new UntappedFetcher()
41
+ const userId = '7de50700-c3f6-48e4-a38d-2add5b0d9b71'
42
+ const playerCode = '76DCDWCZS5FX5PIEEMUVY6GV74'
43
+ const id = `${userId}/${playerCode}`
44
+
45
+ const parts = id.split('/')
46
+ assert.strictEqual(parts.length, 2)
47
+ assert.strictEqual(parts[0], userId)
48
+ assert.strictEqual(parts[1], playerCode)
49
+
50
+ const apiUrl = `https://api.mtga.untapped.gg/api/v1/games/users/${parts[0]}/players/${parts[1]}/?card_set=ECL`
51
+ assert.strictEqual(apiUrl, 'https://api.mtga.untapped.gg/api/v1/games/users/7de50700-c3f6-48e4-a38d-2add5b0d9b71/players/76DCDWCZS5FX5PIEEMUVY6GV74/?card_set=ECL')
52
+
53
+ const profileUrl = `https://mtga.untapped.gg/profile/${parts[0]}/${parts[1]}`
54
+ assert.strictEqual(profileUrl, 'https://mtga.untapped.gg/profile/7de50700-c3f6-48e4-a38d-2add5b0d9b71/76DCDWCZS5FX5PIEEMUVY6GV74')
55
+ })
56
+
57
+
58
+