implement chat tests, fix database usage

This commit is contained in:
Sky Johnson 2025-08-01 16:24:32 -05:00
parent 28f296e546
commit c10bf098c5
3 changed files with 957 additions and 580 deletions

View File

@ -1,487 +0,0 @@
mode: set
eq2emu/internal/alt_advancement/database.go:9.53,20.16 3 0
eq2emu/internal/alt_advancement/database.go:20.16,22.3 1 0
eq2emu/internal/alt_advancement/database.go:23.2,26.18 3 0
eq2emu/internal/alt_advancement/database.go:26.18,54.17 3 0
eq2emu/internal/alt_advancement/database.go:54.17,56.4 1 0
eq2emu/internal/alt_advancement/database.go:59.3,62.65 2 0
eq2emu/internal/alt_advancement/database.go:62.65,64.24 1 0
eq2emu/internal/alt_advancement/database.go:64.24,66.5 1 0
eq2emu/internal/alt_advancement/database.go:67.4,67.12 1 0
eq2emu/internal/alt_advancement/database.go:70.3,70.16 1 0
eq2emu/internal/alt_advancement/database.go:73.2,73.34 1 0
eq2emu/internal/alt_advancement/database.go:73.34,75.3 1 0
eq2emu/internal/alt_advancement/database.go:78.2,80.22 2 0
eq2emu/internal/alt_advancement/database.go:80.22,82.3 1 0
eq2emu/internal/alt_advancement/database.go:84.2,84.12 1 0
eq2emu/internal/alt_advancement/database.go:88.47,95.16 3 0
eq2emu/internal/alt_advancement/database.go:95.16,97.3 1 0
eq2emu/internal/alt_advancement/database.go:98.2,101.18 3 0
eq2emu/internal/alt_advancement/database.go:101.18,109.17 3 0
eq2emu/internal/alt_advancement/database.go:109.17,111.4 1 0
eq2emu/internal/alt_advancement/database.go:114.3,114.61 1 0
eq2emu/internal/alt_advancement/database.go:114.61,116.24 1 0
eq2emu/internal/alt_advancement/database.go:116.24,118.5 1 0
eq2emu/internal/alt_advancement/database.go:119.4,119.12 1 0
eq2emu/internal/alt_advancement/database.go:122.3,122.16 1 0
eq2emu/internal/alt_advancement/database.go:125.2,125.34 1 0
eq2emu/internal/alt_advancement/database.go:125.34,127.3 1 0
eq2emu/internal/alt_advancement/database.go:129.2,129.22 1 0
eq2emu/internal/alt_advancement/database.go:129.22,131.3 1 0
eq2emu/internal/alt_advancement/database.go:133.2,133.12 1 0
eq2emu/internal/alt_advancement/database.go:137.81,148.16 4 0
eq2emu/internal/alt_advancement/database.go:148.16,150.3 1 0
eq2emu/internal/alt_advancement/database.go:151.2,156.18 3 0
eq2emu/internal/alt_advancement/database.go:156.18,166.17 3 0
eq2emu/internal/alt_advancement/database.go:166.17,168.4 1 0
eq2emu/internal/alt_advancement/database.go:170.3,170.47 1 0
eq2emu/internal/alt_advancement/database.go:170.47,172.4 1 0
eq2emu/internal/alt_advancement/database.go:173.3,173.87 1 0
eq2emu/internal/alt_advancement/database.go:176.2,176.34 1 0
eq2emu/internal/alt_advancement/database.go:176.34,178.3 1 0
eq2emu/internal/alt_advancement/database.go:181.2,181.51 1 0
eq2emu/internal/alt_advancement/database.go:181.51,185.3 3 0
eq2emu/internal/alt_advancement/database.go:188.2,189.16 2 0
eq2emu/internal/alt_advancement/database.go:189.16,191.3 1 0
eq2emu/internal/alt_advancement/database.go:194.2,195.16 2 0
eq2emu/internal/alt_advancement/database.go:195.16,197.3 1 0
eq2emu/internal/alt_advancement/database.go:200.2,202.25 2 0
eq2emu/internal/alt_advancement/database.go:206.99,214.16 3 0
eq2emu/internal/alt_advancement/database.go:214.16,216.3 1 0
eq2emu/internal/alt_advancement/database.go:217.2,219.18 2 0
eq2emu/internal/alt_advancement/database.go:219.18,234.17 4 0
eq2emu/internal/alt_advancement/database.go:234.17,236.4 1 0
eq2emu/internal/alt_advancement/database.go:239.3,239.93 1 0
eq2emu/internal/alt_advancement/database.go:239.93,241.4 1 0
eq2emu/internal/alt_advancement/database.go:242.3,242.89 1 0
eq2emu/internal/alt_advancement/database.go:242.89,244.4 1 0
eq2emu/internal/alt_advancement/database.go:246.3,246.53 1 0
eq2emu/internal/alt_advancement/database.go:249.2,249.19 1 0
eq2emu/internal/alt_advancement/database.go:253.97,270.16 4 0
eq2emu/internal/alt_advancement/database.go:270.16,272.50 1 0
eq2emu/internal/alt_advancement/database.go:272.50,279.4 6 0
eq2emu/internal/alt_advancement/database.go:280.3,280.64 1 0
eq2emu/internal/alt_advancement/database.go:283.2,283.12 1 0
eq2emu/internal/alt_advancement/database.go:287.74,289.32 1 0
eq2emu/internal/alt_advancement/database.go:289.32,295.51 4 0
eq2emu/internal/alt_advancement/database.go:295.51,296.27 1 0
eq2emu/internal/alt_advancement/database.go:296.27,298.5 1 0
eq2emu/internal/alt_advancement/database.go:300.3,303.28 3 0
eq2emu/internal/alt_advancement/database.go:308.72,309.24 1 0
eq2emu/internal/alt_advancement/database.go:309.24,311.3 1 0
eq2emu/internal/alt_advancement/database.go:314.2,315.16 2 0
eq2emu/internal/alt_advancement/database.go:315.16,317.3 1 0
eq2emu/internal/alt_advancement/database.go:318.2,322.16 3 0
eq2emu/internal/alt_advancement/database.go:322.16,324.3 1 0
eq2emu/internal/alt_advancement/database.go:327.2,328.16 2 0
eq2emu/internal/alt_advancement/database.go:328.16,330.3 1 0
eq2emu/internal/alt_advancement/database.go:333.2,334.16 2 0
eq2emu/internal/alt_advancement/database.go:334.16,336.3 1 0
eq2emu/internal/alt_advancement/database.go:339.2,339.35 1 0
eq2emu/internal/alt_advancement/database.go:339.35,341.3 1 0
eq2emu/internal/alt_advancement/database.go:344.2,347.12 3 0
eq2emu/internal/alt_advancement/database.go:351.94,369.2 3 0
eq2emu/internal/alt_advancement/database.go:372.96,375.16 2 0
eq2emu/internal/alt_advancement/database.go:375.16,377.3 1 0
eq2emu/internal/alt_advancement/database.go:380.2,386.50 2 0
eq2emu/internal/alt_advancement/database.go:386.50,397.17 2 0
eq2emu/internal/alt_advancement/database.go:397.17,399.4 1 0
eq2emu/internal/alt_advancement/database.go:402.2,402.12 1 0
eq2emu/internal/alt_advancement/database.go:406.97,409.16 2 0
eq2emu/internal/alt_advancement/database.go:409.16,411.3 1 0
eq2emu/internal/alt_advancement/database.go:414.2,419.49 2 0
eq2emu/internal/alt_advancement/database.go:419.49,421.59 1 0
eq2emu/internal/alt_advancement/database.go:421.59,422.43 1 0
eq2emu/internal/alt_advancement/database.go:422.43,431.19 2 0
eq2emu/internal/alt_advancement/database.go:431.19,433.6 1 0
eq2emu/internal/alt_advancement/database.go:438.2,438.12 1 0
eq2emu/internal/alt_advancement/database.go:442.89,450.16 3 0
eq2emu/internal/alt_advancement/database.go:450.16,452.3 1 0
eq2emu/internal/alt_advancement/database.go:453.2,457.18 3 0
eq2emu/internal/alt_advancement/database.go:457.18,467.17 3 0
eq2emu/internal/alt_advancement/database.go:467.17,469.4 1 0
eq2emu/internal/alt_advancement/database.go:471.3,471.41 1 0
eq2emu/internal/alt_advancement/database.go:471.41,473.4 1 0
eq2emu/internal/alt_advancement/database.go:474.3,474.75 1 0
eq2emu/internal/alt_advancement/database.go:477.2,477.34 1 0
eq2emu/internal/alt_advancement/database.go:477.34,479.3 1 0
eq2emu/internal/alt_advancement/database.go:481.2,481.23 1 0
eq2emu/internal/alt_advancement/database.go:485.65,488.16 2 0
eq2emu/internal/alt_advancement/database.go:488.16,490.3 1 0
eq2emu/internal/alt_advancement/database.go:491.2,500.31 3 0
eq2emu/internal/alt_advancement/database.go:500.31,503.17 3 0
eq2emu/internal/alt_advancement/database.go:503.17,505.4 1 0
eq2emu/internal/alt_advancement/database.go:509.2,509.35 1 0
eq2emu/internal/alt_advancement/database.go:509.35,511.3 1 0
eq2emu/internal/alt_advancement/database.go:513.2,513.12 1 0
eq2emu/internal/alt_advancement/database.go:517.75,523.16 4 0
eq2emu/internal/alt_advancement/database.go:523.16,525.3 1 0
eq2emu/internal/alt_advancement/database.go:526.2,531.16 4 0
eq2emu/internal/alt_advancement/database.go:531.16,533.3 1 0
eq2emu/internal/alt_advancement/database.go:534.2,546.16 4 0
eq2emu/internal/alt_advancement/database.go:546.16,548.3 1 0
eq2emu/internal/alt_advancement/database.go:549.2,552.18 3 0
eq2emu/internal/alt_advancement/database.go:552.18,556.17 4 0
eq2emu/internal/alt_advancement/database.go:556.17,558.4 1 0
eq2emu/internal/alt_advancement/database.go:559.3,559.29 1 0
eq2emu/internal/alt_advancement/database.go:561.2,563.19 2 0
eq2emu/internal/alt_advancement/interfaces.go:191.130,198.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:282.77,287.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:290.54,292.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:295.45,297.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:300.79,302.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:305.70,307.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:310.51,312.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:315.77,317.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:320.60,322.2 1 0
eq2emu/internal/alt_advancement/interfaces.go:325.67,327.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:330.62,332.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:335.69,337.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:340.59,342.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:345.42,347.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:355.57,357.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:360.48,362.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:365.52,367.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:370.45,372.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:375.45,377.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:380.54,382.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:385.44,387.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:390.46,392.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:395.67,397.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:405.57,407.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:410.48,412.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:415.52,417.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:420.48,422.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:425.59,427.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:430.54,432.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:446.53,453.2 1 1
eq2emu/internal/alt_advancement/interfaces.go:456.69,460.46 3 1
eq2emu/internal/alt_advancement/interfaces.go:460.46,463.3 2 1
eq2emu/internal/alt_advancement/interfaces.go:465.2,466.19 2 1
eq2emu/internal/alt_advancement/interfaces.go:470.69,474.39 3 1
eq2emu/internal/alt_advancement/interfaces.go:474.39,476.27 1 0
eq2emu/internal/alt_advancement/interfaces.go:476.27,478.9 2 0
eq2emu/internal/alt_advancement/interfaces.go:482.2,482.34 1 1
eq2emu/internal/alt_advancement/interfaces.go:486.52,491.2 3 1
eq2emu/internal/alt_advancement/interfaces.go:494.82,498.58 3 1
eq2emu/internal/alt_advancement/interfaces.go:498.58,501.3 2 1
eq2emu/internal/alt_advancement/interfaces.go:503.2,504.19 2 1
eq2emu/internal/alt_advancement/interfaces.go:508.87,512.45 3 1
eq2emu/internal/alt_advancement/interfaces.go:512.45,514.33 1 0
eq2emu/internal/alt_advancement/interfaces.go:514.33,516.9 2 0
eq2emu/internal/alt_advancement/interfaces.go:520.2,520.43 1 1
eq2emu/internal/alt_advancement/interfaces.go:524.66,529.2 3 0
eq2emu/internal/alt_advancement/interfaces.go:532.73,536.49 3 1
eq2emu/internal/alt_advancement/interfaces.go:536.49,540.3 3 1
eq2emu/internal/alt_advancement/interfaces.go:542.2,543.19 2 1
eq2emu/internal/alt_advancement/interfaces.go:547.75,551.42 3 1
eq2emu/internal/alt_advancement/interfaces.go:551.42,553.30 1 0
eq2emu/internal/alt_advancement/interfaces.go:553.30,555.9 2 0
eq2emu/internal/alt_advancement/interfaces.go:559.2,560.33 2 1
eq2emu/internal/alt_advancement/interfaces.go:564.58,569.2 3 0
eq2emu/internal/alt_advancement/interfaces.go:572.33,579.2 5 1
eq2emu/internal/alt_advancement/interfaces.go:582.59,594.2 3 1
eq2emu/internal/alt_advancement/interfaces.go:597.51,602.2 3 1
eq2emu/internal/alt_advancement/manager.go:9.54,19.2 1 1
eq2emu/internal/alt_advancement/manager.go:22.36,24.40 1 1
eq2emu/internal/alt_advancement/manager.go:24.40,26.3 1 0
eq2emu/internal/alt_advancement/manager.go:29.2,29.34 1 1
eq2emu/internal/alt_advancement/manager.go:29.34,32.3 2 1
eq2emu/internal/alt_advancement/manager.go:34.2,34.54 1 1
eq2emu/internal/alt_advancement/manager.go:34.54,37.3 2 1
eq2emu/internal/alt_advancement/manager.go:39.2,39.12 1 1
eq2emu/internal/alt_advancement/manager.go:43.35,48.24 3 1
eq2emu/internal/alt_advancement/manager.go:48.24,50.3 1 1
eq2emu/internal/alt_advancement/manager.go:52.2,52.12 1 1
eq2emu/internal/alt_advancement/manager.go:56.39,57.9 1 1
eq2emu/internal/alt_advancement/manager.go:58.21,59.15 1 1
eq2emu/internal/alt_advancement/manager.go:60.10,61.14 1 1
eq2emu/internal/alt_advancement/manager.go:66.41,67.24 1 1
eq2emu/internal/alt_advancement/manager.go:67.24,69.3 1 1
eq2emu/internal/alt_advancement/manager.go:71.2,74.58 2 1
eq2emu/internal/alt_advancement/manager.go:74.58,76.3 1 1
eq2emu/internal/alt_advancement/manager.go:79.2,79.52 1 1
eq2emu/internal/alt_advancement/manager.go:79.52,81.3 1 0
eq2emu/internal/alt_advancement/manager.go:84.2,94.12 8 1
eq2emu/internal/alt_advancement/manager.go:98.43,110.16 7 1
eq2emu/internal/alt_advancement/manager.go:110.16,112.3 1 1
eq2emu/internal/alt_advancement/manager.go:114.2,114.12 1 1
eq2emu/internal/alt_advancement/manager.go:118.78,122.2 1 0
eq2emu/internal/alt_advancement/manager.go:125.90,126.24 1 1
eq2emu/internal/alt_advancement/manager.go:126.24,128.3 1 1
eq2emu/internal/alt_advancement/manager.go:130.2,131.16 2 1
eq2emu/internal/alt_advancement/manager.go:131.16,133.3 1 0
eq2emu/internal/alt_advancement/manager.go:135.2,135.25 1 1
eq2emu/internal/alt_advancement/manager.go:139.60,140.24 1 1
eq2emu/internal/alt_advancement/manager.go:140.24,142.3 1 1
eq2emu/internal/alt_advancement/manager.go:145.2,146.24 2 1
eq2emu/internal/alt_advancement/manager.go:146.24,148.3 1 0
eq2emu/internal/alt_advancement/manager.go:151.2,151.46 1 1
eq2emu/internal/alt_advancement/manager.go:155.82,158.65 2 1
eq2emu/internal/alt_advancement/manager.go:158.65,161.3 2 1
eq2emu/internal/alt_advancement/manager.go:162.2,169.65 4 1
eq2emu/internal/alt_advancement/manager.go:169.65,171.3 1 1
eq2emu/internal/alt_advancement/manager.go:174.2,175.16 2 1
eq2emu/internal/alt_advancement/manager.go:175.16,177.3 1 1
eq2emu/internal/alt_advancement/manager.go:180.2,185.25 3 1
eq2emu/internal/alt_advancement/manager.go:189.89,192.16 2 1
eq2emu/internal/alt_advancement/manager.go:192.16,194.3 1 0
eq2emu/internal/alt_advancement/manager.go:197.2,198.19 2 1
eq2emu/internal/alt_advancement/manager.go:198.19,200.3 1 1
eq2emu/internal/alt_advancement/manager.go:203.2,203.25 1 1
eq2emu/internal/alt_advancement/manager.go:203.25,204.90 1 0
eq2emu/internal/alt_advancement/manager.go:204.90,207.4 2 0
eq2emu/internal/alt_advancement/manager.go:211.2,212.16 2 1
eq2emu/internal/alt_advancement/manager.go:212.16,214.3 1 1
eq2emu/internal/alt_advancement/manager.go:217.2,221.24 3 1
eq2emu/internal/alt_advancement/manager.go:221.24,223.3 1 0
eq2emu/internal/alt_advancement/manager.go:226.2,226.26 1 1
eq2emu/internal/alt_advancement/manager.go:226.26,228.3 1 0
eq2emu/internal/alt_advancement/manager.go:230.2,230.12 1 1
eq2emu/internal/alt_advancement/manager.go:234.70,237.24 2 1
eq2emu/internal/alt_advancement/manager.go:237.24,239.3 1 1
eq2emu/internal/alt_advancement/manager.go:242.2,243.19 2 0
eq2emu/internal/alt_advancement/manager.go:243.19,245.3 1 0
eq2emu/internal/alt_advancement/manager.go:248.2,249.42 2 0
eq2emu/internal/alt_advancement/manager.go:249.42,251.3 1 0
eq2emu/internal/alt_advancement/manager.go:254.2,268.24 9 0
eq2emu/internal/alt_advancement/manager.go:268.24,270.3 1 0
eq2emu/internal/alt_advancement/manager.go:273.2,273.26 1 0
eq2emu/internal/alt_advancement/manager.go:273.26,275.3 1 0
eq2emu/internal/alt_advancement/manager.go:277.2,277.12 1 0
eq2emu/internal/alt_advancement/manager.go:281.96,284.24 2 1
eq2emu/internal/alt_advancement/manager.go:284.24,286.3 1 1
eq2emu/internal/alt_advancement/manager.go:289.2,292.28 3 0
eq2emu/internal/alt_advancement/manager.go:292.28,294.40 1 0
eq2emu/internal/alt_advancement/manager.go:294.40,296.4 1 0
eq2emu/internal/alt_advancement/manager.go:299.2,299.26 1 0
eq2emu/internal/alt_advancement/manager.go:303.81,306.24 2 1
eq2emu/internal/alt_advancement/manager.go:306.24,308.3 1 1
eq2emu/internal/alt_advancement/manager.go:311.2,311.25 1 0
eq2emu/internal/alt_advancement/manager.go:311.25,312.86 1 0
eq2emu/internal/alt_advancement/manager.go:312.86,314.4 1 0
eq2emu/internal/alt_advancement/manager.go:318.2,327.12 7 0
eq2emu/internal/alt_advancement/manager.go:331.92,334.24 2 1
eq2emu/internal/alt_advancement/manager.go:334.24,336.3 1 1
eq2emu/internal/alt_advancement/manager.go:339.2,340.21 2 0
eq2emu/internal/alt_advancement/manager.go:340.21,343.3 2 0
eq2emu/internal/alt_advancement/manager.go:343.8,346.3 2 0
eq2emu/internal/alt_advancement/manager.go:348.2,355.12 5 0
eq2emu/internal/alt_advancement/manager.go:359.86,362.24 2 1
eq2emu/internal/alt_advancement/manager.go:362.24,364.3 1 1
eq2emu/internal/alt_advancement/manager.go:367.2,369.50 3 1
eq2emu/internal/alt_advancement/manager.go:369.50,371.3 1 0
eq2emu/internal/alt_advancement/manager.go:372.2,374.23 2 1
eq2emu/internal/alt_advancement/manager.go:378.90,381.16 2 1
eq2emu/internal/alt_advancement/manager.go:381.16,383.3 1 0
eq2emu/internal/alt_advancement/manager.go:386.2,396.24 9 1
eq2emu/internal/alt_advancement/manager.go:396.24,398.3 1 0
eq2emu/internal/alt_advancement/manager.go:401.2,403.12 2 1
eq2emu/internal/alt_advancement/manager.go:407.82,410.24 2 1
eq2emu/internal/alt_advancement/manager.go:410.24,412.3 1 1
eq2emu/internal/alt_advancement/manager.go:414.2,417.91 3 1
eq2emu/internal/alt_advancement/manager.go:421.67,423.19 2 1
eq2emu/internal/alt_advancement/manager.go:423.19,425.3 1 0
eq2emu/internal/alt_advancement/manager.go:426.2,426.20 1 1
eq2emu/internal/alt_advancement/manager.go:430.77,432.19 2 1
eq2emu/internal/alt_advancement/manager.go:432.19,434.3 1 0
eq2emu/internal/alt_advancement/manager.go:435.2,435.20 1 1
eq2emu/internal/alt_advancement/manager.go:439.75,441.2 1 1
eq2emu/internal/alt_advancement/manager.go:444.77,446.2 1 0
eq2emu/internal/alt_advancement/manager.go:449.55,456.2 4 1
eq2emu/internal/alt_advancement/manager.go:459.79,461.24 2 1
eq2emu/internal/alt_advancement/manager.go:461.24,463.3 1 0
eq2emu/internal/alt_advancement/manager.go:465.2,478.3 3 1
eq2emu/internal/alt_advancement/manager.go:482.62,485.2 2 0
eq2emu/internal/alt_advancement/manager.go:488.50,490.2 1 1
eq2emu/internal/alt_advancement/manager.go:495.49,497.2 1 1
eq2emu/internal/alt_advancement/manager.go:500.64,502.2 1 0
eq2emu/internal/alt_advancement/manager.go:505.62,510.2 3 1
eq2emu/internal/alt_advancement/manager.go:513.58,515.2 1 0
eq2emu/internal/alt_advancement/manager.go:518.55,520.2 1 0
eq2emu/internal/alt_advancement/manager.go:523.56,525.2 1 0
eq2emu/internal/alt_advancement/manager.go:528.46,530.2 1 0
eq2emu/internal/alt_advancement/manager.go:535.71,540.2 3 1
eq2emu/internal/alt_advancement/manager.go:543.115,552.46 4 1
eq2emu/internal/alt_advancement/manager.go:552.46,554.3 1 1
eq2emu/internal/alt_advancement/manager.go:557.2,558.21 2 1
eq2emu/internal/alt_advancement/manager.go:558.21,570.3 2 1
eq2emu/internal/alt_advancement/manager.go:573.2,582.12 7 1
eq2emu/internal/alt_advancement/manager.go:586.93,596.29 1 0
eq2emu/internal/alt_advancement/manager.go:596.29,598.64 2 0
eq2emu/internal/alt_advancement/manager.go:598.64,600.4 1 0
eq2emu/internal/alt_advancement/manager.go:603.2,603.13 1 0
eq2emu/internal/alt_advancement/manager.go:609.40,615.6 4 1
eq2emu/internal/alt_advancement/manager.go:615.6,616.10 1 1
eq2emu/internal/alt_advancement/manager.go:617.19,618.25 1 1
eq2emu/internal/alt_advancement/manager.go:619.22,620.10 1 1
eq2emu/internal/alt_advancement/manager.go:626.37,632.6 4 1
eq2emu/internal/alt_advancement/manager.go:632.6,633.10 1 1
eq2emu/internal/alt_advancement/manager.go:634.19,635.28 1 1
eq2emu/internal/alt_advancement/manager.go:636.22,637.10 1 1
eq2emu/internal/alt_advancement/manager.go:643.41,653.46 7 1
eq2emu/internal/alt_advancement/manager.go:653.46,658.3 4 0
eq2emu/internal/alt_advancement/manager.go:659.2,664.32 4 1
eq2emu/internal/alt_advancement/manager.go:664.32,666.3 1 0
eq2emu/internal/alt_advancement/manager.go:668.2,668.39 1 1
eq2emu/internal/alt_advancement/manager.go:672.44,673.24 1 1
eq2emu/internal/alt_advancement/manager.go:673.24,675.3 1 0
eq2emu/internal/alt_advancement/manager.go:677.2,680.46 3 1
eq2emu/internal/alt_advancement/manager.go:680.46,681.28 1 0
eq2emu/internal/alt_advancement/manager.go:681.28,682.64 1 0
eq2emu/internal/alt_advancement/manager.go:682.64,684.13 2 0
eq2emu/internal/alt_advancement/manager.go:686.4,686.33 1 0
eq2emu/internal/alt_advancement/manager.go:692.57,696.19 3 0
eq2emu/internal/alt_advancement/manager.go:697.27,698.30 1 0
eq2emu/internal/alt_advancement/manager.go:699.25,700.28 1 0
eq2emu/internal/alt_advancement/manager.go:701.23,702.26 1 0
eq2emu/internal/alt_advancement/manager.go:709.72,713.43 3 1
eq2emu/internal/alt_advancement/manager.go:713.43,715.3 1 1
eq2emu/internal/alt_advancement/manager.go:719.46,723.43 3 1
eq2emu/internal/alt_advancement/manager.go:723.43,725.3 1 0
eq2emu/internal/alt_advancement/manager.go:729.93,733.43 3 1
eq2emu/internal/alt_advancement/manager.go:733.43,735.3 1 1
eq2emu/internal/alt_advancement/manager.go:739.109,743.43 3 1
eq2emu/internal/alt_advancement/manager.go:743.43,745.3 1 1
eq2emu/internal/alt_advancement/manager.go:749.111,753.43 3 0
eq2emu/internal/alt_advancement/manager.go:753.43,755.3 1 0
eq2emu/internal/alt_advancement/manager.go:759.97,763.43 3 0
eq2emu/internal/alt_advancement/manager.go:763.43,765.3 1 0
eq2emu/internal/alt_advancement/manager.go:769.96,773.43 3 0
eq2emu/internal/alt_advancement/manager.go:773.43,775.3 1 0
eq2emu/internal/alt_advancement/manager.go:779.100,783.43 3 1
eq2emu/internal/alt_advancement/manager.go:783.43,785.3 1 1
eq2emu/internal/alt_advancement/master_list.go:10.38,19.2 1 1
eq2emu/internal/alt_advancement/master_list.go:22.72,23.17 1 1
eq2emu/internal/alt_advancement/master_list.go:23.17,25.3 1 1
eq2emu/internal/alt_advancement/master_list.go:27.2,27.21 1 1
eq2emu/internal/alt_advancement/master_list.go:27.21,29.3 1 1
eq2emu/internal/alt_advancement/master_list.go:31.2,35.56 3 1
eq2emu/internal/alt_advancement/master_list.go:35.56,37.3 1 1
eq2emu/internal/alt_advancement/master_list.go:39.2,39.54 1 1
eq2emu/internal/alt_advancement/master_list.go:39.54,41.3 1 1
eq2emu/internal/alt_advancement/master_list.go:44.2,51.38 4 1
eq2emu/internal/alt_advancement/master_list.go:51.38,53.3 1 1
eq2emu/internal/alt_advancement/master_list.go:54.2,58.12 3 1
eq2emu/internal/alt_advancement/master_list.go:62.75,66.54 3 1
eq2emu/internal/alt_advancement/master_list.go:66.54,68.3 1 1
eq2emu/internal/alt_advancement/master_list.go:70.2,70.12 1 1
eq2emu/internal/alt_advancement/master_list.go:74.82,78.52 3 1
eq2emu/internal/alt_advancement/master_list.go:78.52,80.3 1 1
eq2emu/internal/alt_advancement/master_list.go:82.2,82.12 1 1
eq2emu/internal/alt_advancement/master_list.go:86.70,90.52 3 1
eq2emu/internal/alt_advancement/master_list.go:90.52,93.29 2 1
eq2emu/internal/alt_advancement/master_list.go:93.29,95.4 1 1
eq2emu/internal/alt_advancement/master_list.go:96.3,96.16 1 1
eq2emu/internal/alt_advancement/master_list.go:99.2,99.28 1 1
eq2emu/internal/alt_advancement/master_list.go:103.72,109.32 4 1
eq2emu/internal/alt_advancement/master_list.go:109.32,111.49 1 1
eq2emu/internal/alt_advancement/master_list.go:111.49,113.4 1 1
eq2emu/internal/alt_advancement/master_list.go:116.2,116.15 1 1
eq2emu/internal/alt_advancement/master_list.go:120.70,126.32 4 1
eq2emu/internal/alt_advancement/master_list.go:126.32,127.27 1 1
eq2emu/internal/alt_advancement/master_list.go:127.27,129.4 1 1
eq2emu/internal/alt_advancement/master_list.go:132.2,132.15 1 1
eq2emu/internal/alt_advancement/master_list.go:136.37,141.2 3 1
eq2emu/internal/alt_advancement/master_list.go:144.56,149.32 4 1
eq2emu/internal/alt_advancement/master_list.go:149.32,151.3 1 1
eq2emu/internal/alt_advancement/master_list.go:153.2,153.15 1 1
eq2emu/internal/alt_advancement/master_list.go:157.51,166.2 7 1
eq2emu/internal/alt_advancement/master_list.go:169.43,173.35 3 1
eq2emu/internal/alt_advancement/master_list.go:173.35,174.56 1 1
eq2emu/internal/alt_advancement/master_list.go:174.56,179.26 3 1
eq2emu/internal/alt_advancement/master_list.go:179.26,181.5 1 1
eq2emu/internal/alt_advancement/master_list.go:182.4,182.28 1 1
eq2emu/internal/alt_advancement/master_list.go:188.46,193.2 3 0
eq2emu/internal/alt_advancement/master_list.go:196.45,201.35 4 1
eq2emu/internal/alt_advancement/master_list.go:201.35,203.3 1 1
eq2emu/internal/alt_advancement/master_list.go:205.2,205.41 1 1
eq2emu/internal/alt_advancement/master_list.go:205.41,207.3 1 1
eq2emu/internal/alt_advancement/master_list.go:209.2,209.15 1 1
eq2emu/internal/alt_advancement/master_list.go:213.51,219.32 4 0
eq2emu/internal/alt_advancement/master_list.go:219.32,220.20 1 0
eq2emu/internal/alt_advancement/master_list.go:220.20,222.4 1 0
eq2emu/internal/alt_advancement/master_list.go:225.3,225.26 1 0
eq2emu/internal/alt_advancement/master_list.go:225.26,226.61 1 0
eq2emu/internal/alt_advancement/master_list.go:226.61,228.5 1 0
eq2emu/internal/alt_advancement/master_list.go:232.3,232.49 1 0
eq2emu/internal/alt_advancement/master_list.go:232.49,234.4 1 0
eq2emu/internal/alt_advancement/master_list.go:236.3,236.49 1 0
eq2emu/internal/alt_advancement/master_list.go:236.49,238.4 1 0
eq2emu/internal/alt_advancement/master_list.go:241.3,241.65 1 0
eq2emu/internal/alt_advancement/master_list.go:241.65,243.4 1 0
eq2emu/internal/alt_advancement/master_list.go:245.3,245.61 1 0
eq2emu/internal/alt_advancement/master_list.go:245.61,247.4 1 0
eq2emu/internal/alt_advancement/master_list.go:250.2,250.15 1 0
eq2emu/internal/alt_advancement/master_list.go:254.60,265.43 8 0
eq2emu/internal/alt_advancement/master_list.go:265.43,267.3 1 0
eq2emu/internal/alt_advancement/master_list.go:268.2,270.14 2 0
eq2emu/internal/alt_advancement/master_list.go:274.46,282.2 1 1
eq2emu/internal/alt_advancement/master_list.go:285.69,286.17 1 1
eq2emu/internal/alt_advancement/master_list.go:286.17,288.3 1 1
eq2emu/internal/alt_advancement/master_list.go:290.2,294.56 3 1
eq2emu/internal/alt_advancement/master_list.go:294.56,296.3 1 1
eq2emu/internal/alt_advancement/master_list.go:299.2,305.44 3 1
eq2emu/internal/alt_advancement/master_list.go:305.44,307.3 1 1
eq2emu/internal/alt_advancement/master_list.go:308.2,312.12 3 1
eq2emu/internal/alt_advancement/master_list.go:316.62,322.37 4 1
eq2emu/internal/alt_advancement/master_list.go:322.37,325.3 2 1
eq2emu/internal/alt_advancement/master_list.go:327.2,327.15 1 1
eq2emu/internal/alt_advancement/master_list.go:331.82,335.60 3 1
eq2emu/internal/alt_advancement/master_list.go:335.60,338.33 2 1
eq2emu/internal/alt_advancement/master_list.go:338.33,341.4 2 1
eq2emu/internal/alt_advancement/master_list.go:342.3,342.16 1 1
eq2emu/internal/alt_advancement/master_list.go:345.2,345.26 1 1
eq2emu/internal/alt_advancement/master_list.go:349.71,353.54 3 1
eq2emu/internal/alt_advancement/master_list.go:353.54,356.3 2 1
eq2emu/internal/alt_advancement/master_list.go:358.2,358.12 1 1
eq2emu/internal/alt_advancement/master_list.go:362.42,367.2 3 1
eq2emu/internal/alt_advancement/master_list.go:370.50,378.2 6 1
eq2emu/internal/alt_advancement/master_list.go:381.51,386.2 3 0
eq2emu/internal/alt_advancement/master_list.go:389.52,394.41 4 0
eq2emu/internal/alt_advancement/master_list.go:394.41,396.3 1 0
eq2emu/internal/alt_advancement/master_list.go:398.2,398.42 1 0
eq2emu/internal/alt_advancement/master_list.go:398.42,400.3 1 0
eq2emu/internal/alt_advancement/master_list.go:402.2,402.16 1 0
eq2emu/internal/alt_advancement/master_list.go:406.59,414.37 5 0
eq2emu/internal/alt_advancement/master_list.go:414.37,416.3 1 0
eq2emu/internal/alt_advancement/master_list.go:419.2,420.37 2 0
eq2emu/internal/alt_advancement/master_list.go:420.37,422.24 2 0
eq2emu/internal/alt_advancement/master_list.go:422.24,424.4 1 0
eq2emu/internal/alt_advancement/master_list.go:425.3,425.27 1 0
eq2emu/internal/alt_advancement/master_list.go:428.2,428.15 1 0
eq2emu/internal/alt_advancement/master_list.go:432.65,443.51 8 0
eq2emu/internal/alt_advancement/master_list.go:443.51,445.3 1 0
eq2emu/internal/alt_advancement/master_list.go:446.2,448.14 2 0
eq2emu/internal/alt_advancement/master_list.go:452.76,457.29 3 0
eq2emu/internal/alt_advancement/master_list.go:457.29,458.13 1 0
eq2emu/internal/alt_advancement/master_list.go:458.13,460.4 1 0
eq2emu/internal/alt_advancement/master_list.go:463.2,463.16 1 0
eq2emu/internal/alt_advancement/master_list.go:467.78,470.27 2 0
eq2emu/internal/alt_advancement/master_list.go:470.27,472.3 1 0
eq2emu/internal/alt_advancement/master_list.go:474.2,474.10 1 0
eq2emu/internal/alt_advancement/types.go:283.47,299.2 1 1
eq2emu/internal/alt_advancement/types.go:302.57,316.2 1 1
eq2emu/internal/alt_advancement/types.go:319.62,331.2 1 1
eq2emu/internal/alt_advancement/types.go:334.54,347.2 1 0
eq2emu/internal/alt_advancement/types.go:350.51,353.2 2 1
eq2emu/internal/alt_advancement/types.go:356.43,362.2 1 1
eq2emu/internal/alt_advancement/types.go:365.39,366.15 1 1
eq2emu/internal/alt_advancement/types.go:367.16,368.22 1 1
eq2emu/internal/alt_advancement/types.go:369.19,370.25 1 1
eq2emu/internal/alt_advancement/types.go:371.17,372.24 1 1
eq2emu/internal/alt_advancement/types.go:373.17,374.23 1 1
eq2emu/internal/alt_advancement/types.go:375.21,376.27 1 1
eq2emu/internal/alt_advancement/types.go:377.19,378.25 1 1
eq2emu/internal/alt_advancement/types.go:379.30,380.36 1 1
eq2emu/internal/alt_advancement/types.go:381.17,382.23 1 1
eq2emu/internal/alt_advancement/types.go:383.22,384.28 1 1
eq2emu/internal/alt_advancement/types.go:385.18,386.24 1 1
eq2emu/internal/alt_advancement/types.go:387.10,388.13 1 1
eq2emu/internal/alt_advancement/types.go:393.36,394.47 1 1
eq2emu/internal/alt_advancement/types.go:394.47,396.3 1 1
eq2emu/internal/alt_advancement/types.go:397.2,397.18 1 1
eq2emu/internal/alt_advancement/types.go:401.46,402.57 1 1
eq2emu/internal/alt_advancement/types.go:402.57,404.3 1 1
eq2emu/internal/alt_advancement/types.go:405.2,405.18 1 1
eq2emu/internal/alt_advancement/types.go:409.59,411.2 1 1

906
internal/chat/chat_test.go Normal file
View File

@ -0,0 +1,906 @@
package chat
import (
"context"
"fmt"
"sync"
"testing"
"time"
)
// EntityWithGetID helper type for testing
type EntityWithGetID struct {
id int32
}
func (e *EntityWithGetID) GetID() int32 {
return e.id
}
// Mock implementations for testing
type MockChannelDatabase struct {
channels map[string]ChatChannelData
mu sync.RWMutex
loadErr error
saveErr error
delErr error
}
func NewMockChannelDatabase() *MockChannelDatabase {
return &MockChannelDatabase{
channels: make(map[string]ChatChannelData),
}
}
func (m *MockChannelDatabase) LoadWorldChannels(ctx context.Context) ([]ChatChannelData, error) {
if m.loadErr != nil {
return nil, m.loadErr
}
m.mu.RLock()
defer m.mu.RUnlock()
var channels []ChatChannelData
for _, channel := range m.channels {
channels = append(channels, channel)
}
return channels, nil
}
func (m *MockChannelDatabase) SaveChannel(ctx context.Context, channel ChatChannelData) error {
if m.saveErr != nil {
return m.saveErr
}
m.mu.Lock()
defer m.mu.Unlock()
m.channels[channel.Name] = channel
return nil
}
func (m *MockChannelDatabase) DeleteChannel(ctx context.Context, channelName string) error {
if m.delErr != nil {
return m.delErr
}
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.channels[channelName]; !exists {
return fmt.Errorf("channel not found")
}
delete(m.channels, channelName)
return nil
}
func (m *MockChannelDatabase) SetLoadError(err error) {
m.loadErr = err
}
func (m *MockChannelDatabase) SetSaveError(err error) {
m.saveErr = err
}
func (m *MockChannelDatabase) SetDeleteError(err error) {
m.delErr = err
}
type MockClientManager struct {
sentMessages []ChannelMessage
sentLists map[int32][]ChannelInfo
sentUpdates []ChatUpdate
sentUserLists map[int32][]ChannelMember
connectedClients map[int32]bool
mu sync.RWMutex
}
type ChatUpdate struct {
CharacterID int32
ChannelName string
Action int
CharacterName string
}
func NewMockClientManager() *MockClientManager {
return &MockClientManager{
sentLists: make(map[int32][]ChannelInfo),
sentUserLists: make(map[int32][]ChannelMember),
connectedClients: make(map[int32]bool),
}
}
func (m *MockClientManager) SendChannelList(characterID int32, channels []ChannelInfo) error {
m.mu.Lock()
defer m.mu.Unlock()
m.sentLists[characterID] = channels
return nil
}
func (m *MockClientManager) SendChannelMessage(characterID int32, message ChannelMessage) error {
m.mu.Lock()
defer m.mu.Unlock()
m.sentMessages = append(m.sentMessages, message)
return nil
}
func (m *MockClientManager) SendChannelUpdate(characterID int32, channelName string, action int, characterName string) error {
m.mu.Lock()
defer m.mu.Unlock()
m.sentUpdates = append(m.sentUpdates, ChatUpdate{
CharacterID: characterID,
ChannelName: channelName,
Action: action,
CharacterName: characterName,
})
return nil
}
func (m *MockClientManager) SendChannelUserList(characterID int32, channelName string, members []ChannelMember) error {
m.mu.Lock()
defer m.mu.Unlock()
m.sentUserLists[characterID] = members
return nil
}
func (m *MockClientManager) IsClientConnected(characterID int32) bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.connectedClients[characterID]
}
func (m *MockClientManager) SetClientConnected(characterID int32, connected bool) {
m.mu.Lock()
defer m.mu.Unlock()
m.connectedClients[characterID] = connected
}
func (m *MockClientManager) GetSentMessages() []ChannelMessage {
m.mu.RLock()
defer m.mu.RUnlock()
return append([]ChannelMessage{}, m.sentMessages...)
}
type MockPlayerManager struct {
players map[int32]PlayerInfo
mu sync.RWMutex
}
func NewMockPlayerManager() *MockPlayerManager {
return &MockPlayerManager{
players: make(map[int32]PlayerInfo),
}
}
func (m *MockPlayerManager) GetPlayerInfo(characterID int32) (PlayerInfo, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if player, exists := m.players[characterID]; exists {
return player, nil
}
return PlayerInfo{}, fmt.Errorf("player not found")
}
func (m *MockPlayerManager) ValidatePlayer(characterID int32, levelReq, raceReq, classReq int32) bool {
player, err := m.GetPlayerInfo(characterID)
if err != nil {
return false
}
if levelReq > 0 && player.Level < levelReq {
return false
}
if raceReq > 0 && (raceReq&(1<<player.Race)) == 0 {
return false
}
if classReq > 0 && (classReq&(1<<player.Class)) == 0 {
return false
}
return true
}
func (m *MockPlayerManager) GetPlayerLanguages(characterID int32) ([]int32, error) {
return []int32{0, 1}, nil // Default languages
}
func (m *MockPlayerManager) AddPlayer(player PlayerInfo) {
m.mu.Lock()
defer m.mu.Unlock()
m.players[player.CharacterID] = player
}
type MockLanguageProcessor struct{}
func (m *MockLanguageProcessor) ProcessMessage(senderID, receiverID int32, message string, languageID int32) (string, error) {
return message, nil // No scrambling for tests
}
func (m *MockLanguageProcessor) CanUnderstand(senderID, receiverID int32, languageID int32) bool {
return true // Everyone understands everything in tests
}
func (m *MockLanguageProcessor) GetDefaultLanguage(characterID int32) int32 {
return 0 // Common tongue
}
// Channel tests
func TestNewChannel(t *testing.T) {
channel := NewChannel("test")
if channel == nil {
t.Fatal("NewChannel returned nil")
}
if channel.GetName() != "test" {
t.Errorf("Channel name = %v, want test", channel.GetName())
}
if channel.GetNumClients() != 0 {
t.Errorf("New channel should have 0 clients, got %v", channel.GetNumClients())
}
}
func TestChannelSettersAndGetters(t *testing.T) {
channel := NewChannel("test")
// Test name
channel.SetName("newname")
if channel.GetName() != "newname" {
t.Errorf("SetName failed: got %v, want newname", channel.GetName())
}
// Test password
channel.SetPassword("secret")
if !channel.HasPassword() {
t.Error("HasPassword should return true after SetPassword")
}
if !channel.PasswordMatches("secret") {
t.Error("PasswordMatches should return true for correct password")
}
if channel.PasswordMatches("wrong") {
t.Error("PasswordMatches should return false for incorrect password")
}
// Test type
channel.SetType(ChannelTypeWorld)
if channel.GetType() != ChannelTypeWorld {
t.Errorf("SetType failed: got %v, want %v", channel.GetType(), ChannelTypeWorld)
}
// Test restrictions
channel.SetLevelRestriction(50)
if !channel.CanJoinChannelByLevel(50) {
t.Error("CanJoinChannelByLevel should allow level 50")
}
if channel.CanJoinChannelByLevel(49) {
t.Error("CanJoinChannelByLevel should not allow level 49")
}
// Test race restriction (bitmask)
channel.SetRacesAllowed(1 << 1) // Only race ID 1 allowed
if !channel.CanJoinChannelByRace(1) {
t.Error("CanJoinChannelByRace should allow race 1")
}
if channel.CanJoinChannelByRace(2) {
t.Error("CanJoinChannelByRace should not allow race 2")
}
// Test class restriction (bitmask)
channel.SetClassesAllowed(1 << 3) // Only class ID 3 allowed
if !channel.CanJoinChannelByClass(3) {
t.Error("CanJoinChannelByClass should allow class 3")
}
if channel.CanJoinChannelByClass(4) {
t.Error("CanJoinChannelByClass should not allow class 4")
}
}
func TestChannelMembership(t *testing.T) {
channel := NewChannel("test")
// Test initial state
if channel.IsInChannel(100) {
t.Error("IsInChannel should return false for new channel")
}
if !channel.IsEmpty() {
t.Error("New channel should be empty")
}
// Test joining
err := channel.JoinChannel(100)
if err != nil {
t.Errorf("JoinChannel failed: %v", err)
}
if !channel.IsInChannel(100) {
t.Error("IsInChannel should return true after joining")
}
if channel.GetNumClients() != 1 {
t.Errorf("GetNumClients = %v, want 1", channel.GetNumClients())
}
if channel.IsEmpty() {
t.Error("Channel should not be empty after joining")
}
// Test duplicate join
err = channel.JoinChannel(100)
if err == nil {
t.Error("JoinChannel should fail for duplicate member")
}
// Test multiple members
err = channel.JoinChannel(200)
if err != nil {
t.Errorf("JoinChannel failed for second member: %v", err)
}
if channel.GetNumClients() != 2 {
t.Errorf("GetNumClients = %v, want 2", channel.GetNumClients())
}
// Test GetMembers
members := channel.GetMembers()
if len(members) != 2 {
t.Errorf("GetMembers returned %v members, want 2", len(members))
}
// Verify members contains both IDs
found100, found200 := false, false
for _, id := range members {
if id == 100 {
found100 = true
}
if id == 200 {
found200 = true
}
}
if !found100 || !found200 {
t.Error("GetMembers should contain both member IDs")
}
// Test leaving
err = channel.LeaveChannel(100)
if err != nil {
t.Errorf("LeaveChannel failed: %v", err)
}
if channel.IsInChannel(100) {
t.Error("IsInChannel should return false after leaving")
}
if channel.GetNumClients() != 1 {
t.Errorf("GetNumClients = %v, want 1 after leaving", channel.GetNumClients())
}
// Test leaving non-member
err = channel.LeaveChannel(300)
if err == nil {
t.Error("LeaveChannel should fail for non-member")
}
// Test leaving last member
err = channel.LeaveChannel(200)
if err != nil {
t.Errorf("LeaveChannel failed for last member: %v", err)
}
if !channel.IsEmpty() {
t.Error("Channel should be empty after all members leave")
}
}
func TestChannelValidateJoin(t *testing.T) {
channel := NewChannel("test")
channel.SetPassword("secret")
channel.SetLevelRestriction(10)
channel.SetRacesAllowed(1 << 1) // Race 1 only
channel.SetClassesAllowed(1 << 2) // Class 2 only
tests := []struct {
name string
level int32
race int32
class int32
password string
wantErr bool
}{
{
name: "valid join",
level: 15,
race: 1,
class: 2,
password: "secret",
wantErr: false,
},
{
name: "wrong password",
level: 15,
race: 1,
class: 2,
password: "wrong",
wantErr: true,
},
{
name: "level too low",
level: 5,
race: 1,
class: 2,
password: "secret",
wantErr: true,
},
{
name: "wrong race",
level: 15,
race: 2,
class: 2,
password: "secret",
wantErr: true,
},
{
name: "wrong class",
level: 15,
race: 1,
class: 3,
password: "secret",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := channel.ValidateJoin(tt.level, tt.race, tt.class, tt.password)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateJoin() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestChannelGetChannelInfo(t *testing.T) {
channel := NewChannel("testchannel")
channel.SetType(ChannelTypeWorld)
channel.SetPassword("secret")
channel.SetLevelRestriction(20)
channel.SetRacesAllowed(15) // Multiple races
channel.SetClassesAllowed(31) // Multiple classes
channel.JoinChannel(100)
channel.JoinChannel(200)
info := channel.GetChannelInfo()
if info.Name != "testchannel" {
t.Errorf("ChannelInfo Name = %v, want testchannel", info.Name)
}
if !info.HasPassword {
t.Error("ChannelInfo should indicate has password")
}
if info.MemberCount != 2 {
t.Errorf("ChannelInfo MemberCount = %v, want 2", info.MemberCount)
}
if info.LevelRestriction != 20 {
t.Errorf("ChannelInfo LevelRestriction = %v, want 20", info.LevelRestriction)
}
if info.ChannelType != ChannelTypeWorld {
t.Errorf("ChannelInfo ChannelType = %v, want %v", info.ChannelType, ChannelTypeWorld)
}
}
func TestChannelCopy(t *testing.T) {
original := NewChannel("original")
original.SetPassword("secret")
original.SetType(ChannelTypeCustom)
original.SetLevelRestriction(25)
original.JoinChannel(100)
original.JoinChannel(200)
copy := original.Copy()
if copy == original {
t.Error("Copy should return different instance")
}
if copy.GetName() != original.GetName() {
t.Error("Copy should have same name")
}
if copy.GetType() != original.GetType() {
t.Error("Copy should have same type")
}
if copy.GetNumClients() != original.GetNumClients() {
t.Error("Copy should have same member count")
}
// Test that modifying copy doesn't affect original
copy.JoinChannel(300)
if original.GetNumClients() == copy.GetNumClients() {
t.Error("Modifying copy should not affect original")
}
}
// Database tests
func TestMockChannelDatabase(t *testing.T) {
db := NewMockChannelDatabase()
ctx := context.Background()
// Test empty database
channels, err := db.LoadWorldChannels(ctx)
if err != nil {
t.Errorf("LoadWorldChannels failed: %v", err)
}
if len(channels) != 0 {
t.Errorf("Expected 0 channels, got %v", len(channels))
}
// Test saving channel
testChannel := ChatChannelData{
Name: "testchannel",
Password: "secret",
LevelRestriction: 10,
ClassRestriction: 15,
RaceRestriction: 7,
}
err = db.SaveChannel(ctx, testChannel)
if err != nil {
t.Errorf("SaveChannel failed: %v", err)
}
// Test loading after save
channels, err = db.LoadWorldChannels(ctx)
if err != nil {
t.Errorf("LoadWorldChannels failed: %v", err)
}
if len(channels) != 1 {
t.Errorf("Expected 1 channel, got %v", len(channels))
}
if channels[0].Name != testChannel.Name {
t.Errorf("Channel name = %v, want %v", channels[0].Name, testChannel.Name)
}
// Test delete
err = db.DeleteChannel(ctx, "testchannel")
if err != nil {
t.Errorf("DeleteChannel failed: %v", err)
}
// Test delete non-existent
err = db.DeleteChannel(ctx, "nonexistent")
if err == nil {
t.Error("DeleteChannel should fail for non-existent channel")
}
// Test error conditions
db.SetLoadError(fmt.Errorf("load error"))
_, err = db.LoadWorldChannels(ctx)
if err == nil {
t.Error("LoadWorldChannels should return error when set")
}
db.SetSaveError(fmt.Errorf("save error"))
err = db.SaveChannel(ctx, testChannel)
if err == nil {
t.Error("SaveChannel should return error when set")
}
db.SetDeleteError(fmt.Errorf("delete error"))
err = db.DeleteChannel(ctx, "test")
if err == nil {
t.Error("DeleteChannel should return error when set")
}
}
// Interface tests
func TestEntityChatAdapter(t *testing.T) {
playerManager := NewMockPlayerManager()
playerManager.AddPlayer(PlayerInfo{
CharacterID: 100,
CharacterName: "TestPlayer",
Level: 25,
Race: 1,
Class: 2,
IsOnline: true,
})
entityWithGetID := &EntityWithGetID{id: 100}
adapter := &EntityChatAdapter{
entity: entityWithGetID,
playerManager: playerManager,
}
if adapter.GetCharacterID() != 100 {
t.Errorf("GetCharacterID() = %v, want 100", adapter.GetCharacterID())
}
if adapter.GetCharacterName() != "TestPlayer" {
t.Errorf("GetCharacterName() = %v, want TestPlayer", adapter.GetCharacterName())
}
if adapter.GetLevel() != 25 {
t.Errorf("GetLevel() = %v, want 25", adapter.GetLevel())
}
if adapter.GetRace() != 1 {
t.Errorf("GetRace() = %v, want 1", adapter.GetRace())
}
if adapter.GetClass() != 2 {
t.Errorf("GetClass() = %v, want 2", adapter.GetClass())
}
// Test with non-existent player
entityWithGetID2 := &EntityWithGetID{id: 999}
adapter2 := &EntityChatAdapter{
entity: entityWithGetID2,
playerManager: playerManager,
}
if adapter2.GetCharacterName() != "" {
t.Error("GetCharacterName should return empty string for non-existent player")
}
if adapter2.GetLevel() != 0 {
t.Error("GetLevel should return 0 for non-existent player")
}
}
// Constants tests
func TestConstants(t *testing.T) {
// Test channel types
if ChannelTypeNone != 0 {
t.Errorf("ChannelTypeNone = %v, want 0", ChannelTypeNone)
}
if ChannelTypeWorld != 1 {
t.Errorf("ChannelTypeWorld = %v, want 1", ChannelTypeWorld)
}
if ChannelTypeCustom != 2 {
t.Errorf("ChannelTypeCustom = %v, want 2", ChannelTypeCustom)
}
// Test chat actions
if ChatChannelJoin != 0 {
t.Errorf("ChatChannelJoin = %v, want 0", ChatChannelJoin)
}
if ChatChannelLeave != 1 {
t.Errorf("ChatChannelLeave = %v, want 1", ChatChannelLeave)
}
// Test restrictions
if NoLevelRestriction != 0 {
t.Errorf("NoLevelRestriction = %v, want 0", NoLevelRestriction)
}
if NoRaceRestriction != 0 {
t.Errorf("NoRaceRestriction = %v, want 0", NoRaceRestriction)
}
if NoClassRestriction != 0 {
t.Errorf("NoClassRestriction = %v, want 0", NoClassRestriction)
}
}
// Concurrency tests
func TestChannelConcurrency(t *testing.T) {
channel := NewChannel("concurrent")
var wg sync.WaitGroup
// Concurrent joins
for i := range 100 {
wg.Add(1)
go func(id int32) {
defer wg.Done()
channel.JoinChannel(id)
}(int32(i))
}
// Concurrent reads
for i := range 50 {
wg.Add(1)
go func(id int32) {
defer wg.Done()
channel.IsInChannel(id)
channel.GetMembers()
channel.GetChannelInfo()
}(int32(i))
}
wg.Wait()
// Verify final state
if channel.GetNumClients() != 100 {
t.Errorf("After concurrent joins, got %v members, want 100", channel.GetNumClients())
}
// Concurrent leaves
for i := range 50 {
wg.Add(1)
go func(id int32) {
defer wg.Done()
channel.LeaveChannel(id)
}(int32(i))
}
wg.Wait()
if channel.GetNumClients() != 50 {
t.Errorf("After concurrent leaves, got %v members, want 50", channel.GetNumClients())
}
}
func TestMockClientManager(t *testing.T) {
client := NewMockClientManager()
// Test client connection status
if client.IsClientConnected(100) {
t.Error("IsClientConnected should return false initially")
}
client.SetClientConnected(100, true)
if !client.IsClientConnected(100) {
t.Error("IsClientConnected should return true after setting")
}
// Test channel list
channels := []ChannelInfo{
{Name: "test1", MemberCount: 5},
{Name: "test2", MemberCount: 10},
}
err := client.SendChannelList(100, channels)
if err != nil {
t.Errorf("SendChannelList failed: %v", err)
}
// Test channel message
message := ChannelMessage{
SenderID: 200,
SenderName: "TestSender",
Message: "Hello world",
ChannelName: "test",
Timestamp: time.Now(),
}
err = client.SendChannelMessage(100, message)
if err != nil {
t.Errorf("SendChannelMessage failed: %v", err)
}
sentMessages := client.GetSentMessages()
if len(sentMessages) != 1 {
t.Errorf("Expected 1 sent message, got %v", len(sentMessages))
}
if sentMessages[0].Message != "Hello world" {
t.Errorf("Sent message = %v, want Hello world", sentMessages[0].Message)
}
}
func TestMockPlayerManager(t *testing.T) {
playerMgr := NewMockPlayerManager()
// Test non-existent player
_, err := playerMgr.GetPlayerInfo(999)
if err == nil {
t.Error("GetPlayerInfo should fail for non-existent player")
}
// Add player
player := PlayerInfo{
CharacterID: 100,
CharacterName: "TestPlayer",
Level: 30,
Race: 2,
Class: 4,
IsOnline: true,
}
playerMgr.AddPlayer(player)
// Test existing player
retrieved, err := playerMgr.GetPlayerInfo(100)
if err != nil {
t.Errorf("GetPlayerInfo failed: %v", err)
}
if retrieved.CharacterName != "TestPlayer" {
t.Errorf("Player name = %v, want TestPlayer", retrieved.CharacterName)
}
// Test validation
if !playerMgr.ValidatePlayer(100, 25, 0, 0) {
t.Error("ValidatePlayer should pass for level requirement")
}
if playerMgr.ValidatePlayer(100, 35, 0, 0) {
t.Error("ValidatePlayer should fail for level requirement")
}
// Test languages
languages, err := playerMgr.GetPlayerLanguages(100)
if err != nil {
t.Errorf("GetPlayerLanguages failed: %v", err)
}
if len(languages) == 0 {
t.Error("GetPlayerLanguages should return some languages")
}
}
func TestMockLanguageProcessor(t *testing.T) {
processor := &MockLanguageProcessor{}
// Test message processing
processed, err := processor.ProcessMessage(100, 200, "test message", 0)
if err != nil {
t.Errorf("ProcessMessage failed: %v", err)
}
if processed != "test message" {
t.Errorf("ProcessMessage = %v, want test message", processed)
}
// Test understanding
if !processor.CanUnderstand(100, 200, 0) {
t.Error("CanUnderstand should return true in mock")
}
// Test default language
if processor.GetDefaultLanguage(100) != 0 {
t.Error("GetDefaultLanguage should return 0 in mock")
}
}
// Benchmarks
func BenchmarkChannelJoin(b *testing.B) {
channel := NewChannel("benchmark")
for i := 0; b.Loop(); i++ {
channel.JoinChannel(int32(i))
}
}
func BenchmarkChannelIsInChannel(b *testing.B) {
channel := NewChannel("benchmark")
for i := range 1000 {
channel.JoinChannel(int32(i))
}
for i := 0; b.Loop(); i++ {
channel.IsInChannel(int32(i % 1000))
}
}
func BenchmarkChannelGetMembers(b *testing.B) {
channel := NewChannel("benchmark")
for i := range 1000 {
channel.JoinChannel(int32(i))
}
for b.Loop() {
channel.GetMembers()
}
}

View File

@ -7,12 +7,12 @@ import (
"eq2emu/internal/database" "eq2emu/internal/database"
) )
// DatabaseChannelManager implements ChannelDatabase interface using the existing database wrapper // DatabaseChannelManager implements ChannelDatabase interface using the correct database wrapper
type DatabaseChannelManager struct { type DatabaseChannelManager struct {
db *database.DB db *database.DB
} }
// NewDatabaseChannelManager creates a new database channel manager // NewDatabaseChannelManager creates a new database channel manager using the correct wrapper
func NewDatabaseChannelManager(db *database.DB) *DatabaseChannelManager { func NewDatabaseChannelManager(db *database.DB) *DatabaseChannelManager {
return &DatabaseChannelManager{ return &DatabaseChannelManager{
db: db, db: db,
@ -23,38 +23,25 @@ func NewDatabaseChannelManager(db *database.DB) *DatabaseChannelManager {
func (dcm *DatabaseChannelManager) LoadWorldChannels(ctx context.Context) ([]ChatChannelData, error) { func (dcm *DatabaseChannelManager) LoadWorldChannels(ctx context.Context) ([]ChatChannelData, error) {
query := "SELECT `name`, `password`, `level_restriction`, `classes`, `races` FROM `channels`" query := "SELECT `name`, `password`, `level_restriction`, `classes`, `races` FROM `channels`"
rows, err := dcm.db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("failed to query channels: %w", err)
}
defer rows.Close()
var channels []ChatChannelData var channels []ChatChannelData
for rows.Next() {
err := dcm.db.Query(query, func(row *database.Row) error {
var channel ChatChannelData var channel ChatChannelData
var password *string
err := rows.Scan( channel.Name = row.Text(0)
&channel.Name, if !row.IsNull(1) {
&password, channel.Password = row.Text(1)
&channel.LevelRestriction,
&channel.ClassRestriction,
&channel.RaceRestriction,
)
if err != nil {
return nil, fmt.Errorf("failed to scan channel row: %w", err)
}
// Handle nullable password field
if password != nil {
channel.Password = *password
} }
channel.LevelRestriction = int32(row.Int64(2))
channel.ClassRestriction = int32(row.Int64(3))
channel.RaceRestriction = int32(row.Int64(4))
channels = append(channels, channel) channels = append(channels, channel)
} return nil
})
if err := rows.Err(); err != nil { if err != nil {
return nil, fmt.Errorf("error iterating channel rows: %w", err) return nil, fmt.Errorf("failed to query channels: %w", err)
} }
return channels, nil return channels, nil
@ -73,7 +60,7 @@ func (dcm *DatabaseChannelManager) SaveChannel(ctx context.Context, channel Chat
password = &channel.Password password = &channel.Password
} }
_, err := dcm.db.ExecContext(ctx, query, err := dcm.db.Exec(query,
channel.Name, channel.Name,
password, password,
channel.LevelRestriction, channel.LevelRestriction,
@ -91,20 +78,11 @@ func (dcm *DatabaseChannelManager) SaveChannel(ctx context.Context, channel Chat
func (dcm *DatabaseChannelManager) DeleteChannel(ctx context.Context, channelName string) error { func (dcm *DatabaseChannelManager) DeleteChannel(ctx context.Context, channelName string) error {
query := "DELETE FROM channels WHERE name = ?" query := "DELETE FROM channels WHERE name = ?"
result, err := dcm.db.ExecContext(ctx, query, channelName) err := dcm.db.Exec(query, channelName)
if err != nil { if err != nil {
return fmt.Errorf("failed to delete channel %s: %w", channelName, err) return fmt.Errorf("failed to delete channel %s: %w", channelName, err)
} }
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to check rows affected for channel %s: %w", channelName, err)
}
if rowsAffected == 0 {
return fmt.Errorf("channel %s not found in database", channelName)
}
return nil return nil
} }
@ -121,7 +99,7 @@ func (dcm *DatabaseChannelManager) EnsureChannelsTable(ctx context.Context) erro
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)` )`
_, err := dcm.db.ExecContext(ctx, query) err := dcm.db.Exec(query)
if err != nil { if err != nil {
return fmt.Errorf("failed to create channels table: %w", err) return fmt.Errorf("failed to create channels table: %w", err)
} }
@ -133,11 +111,16 @@ func (dcm *DatabaseChannelManager) EnsureChannelsTable(ctx context.Context) erro
func (dcm *DatabaseChannelManager) GetChannelCount(ctx context.Context) (int, error) { func (dcm *DatabaseChannelManager) GetChannelCount(ctx context.Context) (int, error) {
query := "SELECT COUNT(*) FROM channels" query := "SELECT COUNT(*) FROM channels"
var count int row, err := dcm.db.QueryRow(query)
err := dcm.db.QueryRowContext(ctx, query).Scan(&count)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to get channel count: %w", err) return 0, fmt.Errorf("failed to query channel count: %w", err)
} }
if row == nil {
return 0, nil
}
defer row.Close()
count := row.Int(0)
return count, nil return count, nil
} }
@ -146,24 +129,24 @@ func (dcm *DatabaseChannelManager) GetChannelCount(ctx context.Context) (int, er
func (dcm *DatabaseChannelManager) GetChannelByName(ctx context.Context, channelName string) (*ChatChannelData, error) { func (dcm *DatabaseChannelManager) GetChannelByName(ctx context.Context, channelName string) (*ChatChannelData, error) {
query := "SELECT `name`, `password`, `level_restriction`, `classes`, `races` FROM `channels` WHERE `name` = ?" query := "SELECT `name`, `password`, `level_restriction`, `classes`, `races` FROM `channels` WHERE `name` = ?"
var channel ChatChannelData row, err := dcm.db.QueryRow(query, channelName)
var password *string
err := dcm.db.QueryRowContext(ctx, query, channelName).Scan(
&channel.Name,
&password,
&channel.LevelRestriction,
&channel.ClassRestriction,
&channel.RaceRestriction,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get channel %s: %w", channelName, err) return nil, fmt.Errorf("failed to query channel %s: %w", channelName, err)
} }
if row == nil {
return nil, fmt.Errorf("channel %s not found", channelName)
}
defer row.Close()
// Handle nullable password field var channel ChatChannelData
if password != nil {
channel.Password = *password channel.Name = row.Text(0)
if !row.IsNull(1) {
channel.Password = row.Text(1)
} }
channel.LevelRestriction = int32(row.Int64(2))
channel.ClassRestriction = int32(row.Int64(3))
channel.RaceRestriction = int32(row.Int64(4))
return &channel, nil return &channel, nil
} }
@ -172,24 +155,17 @@ func (dcm *DatabaseChannelManager) GetChannelByName(ctx context.Context, channel
func (dcm *DatabaseChannelManager) ListChannelNames(ctx context.Context) ([]string, error) { func (dcm *DatabaseChannelManager) ListChannelNames(ctx context.Context) ([]string, error) {
query := "SELECT name FROM channels ORDER BY name" query := "SELECT name FROM channels ORDER BY name"
rows, err := dcm.db.QueryContext(ctx, query) var names []string
err := dcm.db.Query(query, func(row *database.Row) error {
name := row.Text(0)
names = append(names, name)
return nil
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query channel names: %w", err) return nil, fmt.Errorf("failed to query channel names: %w", err)
} }
defer rows.Close()
var names []string
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, fmt.Errorf("failed to scan channel name: %w", err)
}
names = append(names, name)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating channel name rows: %w", err)
}
return names, nil return names, nil
} }
@ -203,20 +179,11 @@ func (dcm *DatabaseChannelManager) UpdateChannelPassword(ctx context.Context, ch
passwordParam = &password passwordParam = &password
} }
result, err := dcm.db.ExecContext(ctx, query, passwordParam, channelName) err := dcm.db.Exec(query, passwordParam, channelName)
if err != nil { if err != nil {
return fmt.Errorf("failed to update password for channel %s: %w", channelName, err) return fmt.Errorf("failed to update password for channel %s: %w", channelName, err)
} }
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to check rows affected for channel %s: %w", channelName, err)
}
if rowsAffected == 0 {
return fmt.Errorf("channel %s not found in database", channelName)
}
return nil return nil
} }
@ -224,19 +191,10 @@ func (dcm *DatabaseChannelManager) UpdateChannelPassword(ctx context.Context, ch
func (dcm *DatabaseChannelManager) UpdateChannelRestrictions(ctx context.Context, channelName string, levelRestriction, classRestriction, raceRestriction int32) error { func (dcm *DatabaseChannelManager) UpdateChannelRestrictions(ctx context.Context, channelName string, levelRestriction, classRestriction, raceRestriction int32) error {
query := "UPDATE channels SET level_restriction = ?, classes = ?, races = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?" query := "UPDATE channels SET level_restriction = ?, classes = ?, races = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?"
result, err := dcm.db.ExecContext(ctx, query, levelRestriction, classRestriction, raceRestriction, channelName) err := dcm.db.Exec(query, levelRestriction, classRestriction, raceRestriction, channelName)
if err != nil { if err != nil {
return fmt.Errorf("failed to update restrictions for channel %s: %w", channelName, err) return fmt.Errorf("failed to update restrictions for channel %s: %w", channelName, err)
} }
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to check rows affected for channel %s: %w", channelName, err)
}
if rowsAffected == 0 {
return fmt.Errorf("channel %s not found in database", channelName)
}
return nil return nil
} }