11const test = require ( 'node:test' ) ;
22const assert = require ( 'assert' ) ;
3+ const fs = require ( 'fs' ) ;
34const { Application, MailSystem } = require ( './main' ) ;
45
5- // TODO: write your tests here
6- // Remember to use Stub, Mock, and Spy when necessary
6+ // Helper: create Application without triggering file read error.
7+ // Override getNames so that it immediately returns a resolved promise.
8+ function createTestApplicationWithoutFileRead ( ) {
9+ const originalGetNames = Application . prototype . getNames ;
10+ Application . prototype . getNames = function ( ) {
11+ return Promise . resolve ( [ this . people , this . selected ] ) ;
12+ } ;
13+ const app = new Application ( ) ;
14+ // Immediately restore the original getNames for any later use.
15+ Application . prototype . getNames = originalGetNames ;
16+ return app ;
17+ }
18+
19+ // Utility function for a small delay.
20+ function delay ( ms ) {
21+ return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
22+ }
23+
24+ // Test: Application.getNames should read names from file.
25+ test ( 'Application.getNames should read names from file' , async ( ) => {
26+ const testData = "Alice\nBob\nCharlie" ;
27+ fs . writeFileSync ( 'name_list.txt' , testData , 'utf8' ) ;
28+
29+ // Use the real getNames.
30+ const app = new Application ( ) ;
31+ const [ people , selected ] = await app . getNames ( ) ;
32+ assert . deepStrictEqual ( people , [ 'Alice' , 'Bob' , 'Charlie' ] ) ;
33+
34+ fs . unlinkSync ( 'name_list.txt' ) ;
35+ } ) ;
36+
37+ // Test: Application constructor should initialize people and selected.
38+ test ( 'Application constructor should initialize people and selected' , async ( ) => {
39+ const testData = "Alice\nBob" ;
40+ fs . writeFileSync ( 'name_list.txt' , testData , 'utf8' ) ;
41+
42+ const app = new Application ( ) ;
43+ // Wait a bit for the asynchronous constructor to complete.
44+ await delay ( 20 ) ;
45+ assert . deepStrictEqual ( app . people , [ 'Alice' , 'Bob' ] ) ;
46+ assert . deepStrictEqual ( app . selected , [ ] ) ;
47+
48+ fs . unlinkSync ( 'name_list.txt' ) ;
49+ } ) ;
50+
51+ // Test: getRandomPerson returns a valid name.
52+ test ( 'Application.getRandomPerson should return a valid name' , ( ) => {
53+ const app = createTestApplicationWithoutFileRead ( ) ;
54+ app . people = [ 'Alice' , 'Bob' , 'Charlie' ] ;
55+
56+ const originalRandom = Math . random ;
57+ Math . random = ( ) => 0.5 ; // floor(0.5 * 3) = 1, expecting 'Bob'
58+
59+ const person = app . getRandomPerson ( ) ;
60+ assert . strictEqual ( person , 'Bob' ) ;
61+
62+ Math . random = originalRandom ;
63+ } ) ;
64+
65+ // Test: selectNextPerson avoids duplicate selections.
66+ test ( 'Application.selectNextPerson should avoid duplicates' , ( ) => {
67+ const app = createTestApplicationWithoutFileRead ( ) ;
68+ app . people = [ 'Alice' , 'Bob' , 'Charlie' ] ;
69+ app . selected = [ 'Alice' ] ;
70+
71+ const originalRandom = Math . random ;
72+ // Force Math.random to return a value that selects 'Charlie'
73+ Math . random = ( ) => 0.8 ; // floor(0.8 * 3) = 2 -> 'Charlie'
74+
75+ const person = app . selectNextPerson ( ) ;
76+ assert . strictEqual ( person , 'Charlie' ) ;
77+ assert . strictEqual ( app . selected . length , 2 ) ;
78+
79+ Math . random = originalRandom ;
80+ } ) ;
81+
82+ // Test: selectNextPerson returns null if all persons have been selected.
83+ test ( 'Application.selectNextPerson should return null if all selected' , ( ) => {
84+ const app = createTestApplicationWithoutFileRead ( ) ;
85+ app . people = [ 'Alice' , 'Bob' ] ;
86+ app . selected = [ 'Alice' , 'Bob' ] ;
87+
88+ const person = app . selectNextPerson ( ) ;
89+ assert . strictEqual ( person , null ) ;
90+ } ) ;
91+
92+ // Test: selectNextPerson loops until a non-duplicate is selected.
93+ test ( 'Application.selectNextPerson should loop until a non-duplicate is selected' , ( ) => {
94+ const app = createTestApplicationWithoutFileRead ( ) ;
95+ app . people = [ 'Alice' , 'Bob' , 'Charlie' ] ;
96+ app . selected = [ 'Alice' ] ; // 'Alice' already selected
97+
98+ // Override getRandomPerson to simulate a duplicate on first call,
99+ // then a unique name on the second call.
100+ let callCount = 0 ;
101+ app . getRandomPerson = function ( ) {
102+ callCount ++ ;
103+ if ( callCount === 1 ) {
104+ return 'Alice' ; // duplicate value
105+ } else {
106+ return 'Bob' ; // new value
107+ }
108+ } ;
109+
110+ const selectedPerson = app . selectNextPerson ( ) ;
111+ assert . strictEqual ( selectedPerson , 'Bob' ) ;
112+ assert . strictEqual ( callCount , 2 ) ; // getRandomPerson should be called twice
113+ } ) ;
114+
115+ // Test: MailSystem.write generates the correct mail content.
116+ test ( 'MailSystem.write should generate correct mail content' , ( ) => {
117+ const mailSystem = new MailSystem ( ) ;
118+ const content = mailSystem . write ( 'Alice' ) ;
119+ assert . strictEqual ( content , 'Congrats, Alice!' ) ;
120+ } ) ;
121+
122+ // Test: MailSystem.send returns true when Math.random is high.
123+ test ( 'MailSystem.send should return true when Math.random is high' , ( ) => {
124+ const mailSystem = new MailSystem ( ) ;
125+ const originalRandom = Math . random ;
126+ Math . random = ( ) => 0.9 ; // Simulate success
127+
128+ const result = mailSystem . send ( 'Alice' , 'Congrats, Alice!' ) ;
129+ assert . strictEqual ( result , true ) ;
130+
131+ Math . random = originalRandom ;
132+ } ) ;
133+
134+ // Test: MailSystem.send returns false when Math.random is low.
135+ test ( 'MailSystem.send should return false when Math.random is low' , ( ) => {
136+ const mailSystem = new MailSystem ( ) ;
137+ const originalRandom = Math . random ;
138+ Math . random = ( ) => 0.1 ; // Simulate failure
139+
140+ const result = mailSystem . send ( 'Alice' , 'Congrats, Alice!' ) ;
141+ assert . strictEqual ( result , false ) ;
142+
143+ Math . random = originalRandom ;
144+ } ) ;
145+
146+ // Test: notifySelected calls write and send for each selected person.
147+ test ( 'Application.notifySelected should call write and send for each person' , ( ) => {
148+ const app = createTestApplicationWithoutFileRead ( ) ;
149+ app . selected = [ 'Alice' , 'Bob' ] ;
150+
151+ let writeCalls = [ ] ;
152+ let sendCalls = [ ] ;
153+
154+ const originalWrite = app . mailSystem . write ;
155+ const originalSend = app . mailSystem . send ;
156+
157+ // Override write and send to record calls.
158+ app . mailSystem . write = function ( name ) {
159+ writeCalls . push ( name ) ;
160+ return 'Congrats!' ;
161+ } ;
162+
163+ app . mailSystem . send = function ( name , context ) {
164+ sendCalls . push ( { name, context } ) ;
165+ return true ;
166+ } ;
167+
168+ app . notifySelected ( ) ;
169+
170+ assert . deepStrictEqual ( writeCalls , [ 'Alice' , 'Bob' ] ) ;
171+ assert . strictEqual ( sendCalls . length , 2 ) ;
172+ assert . deepStrictEqual ( sendCalls [ 0 ] , { name : 'Alice' , context : 'Congrats!' } ) ;
173+ assert . deepStrictEqual ( sendCalls [ 1 ] , { name : 'Bob' , context : 'Congrats!' } ) ;
174+
175+ app . mailSystem . write = originalWrite ;
176+ app . mailSystem . send = originalSend ;
177+ } ) ;
0 commit comments