@@ -112,5 +112,359 @@ final class BuilderTests: XCTestCase {
112112 // When/Then - Invalid value
113113 XCTAssertThrowsError ( try ValidatingBuilder ( ) . setValue ( - 1 ) . build ( ) )
114114 }
115+
116+ func testBaseBuilderNotImplemented( ) {
117+ // Given
118+ struct TestObject {
119+ let value : String
120+ }
121+
122+ class TestBuilder : BaseBuilder < TestObject > {
123+ // Doesn't override build()
124+ }
125+
126+ // When/Then
127+ XCTAssertThrowsError ( try TestBuilder ( ) . build ( ) ) { error in
128+ if case BuilderError . notImplemented = error {
129+ // Expected error
130+ } else {
131+ XCTFail ( " Expected BuilderError.notImplemented, got \( error) " )
132+ }
133+ }
134+ }
135+
136+ func testBuilderErrorLocalizedDescription( ) {
137+ // Test notImplemented
138+ let notImplemented = BuilderError . notImplemented
139+ XCTAssertEqual ( notImplemented. localizedDescription, " Builder build() method not implemented " )
140+
141+ // Test missingRequiredProperty
142+ let missing = BuilderError . missingRequiredProperty ( " testProperty " )
143+ XCTAssertEqual ( missing. localizedDescription, " Required property 'testProperty' is missing " )
144+
145+ // Test invalidValue
146+ let invalid = BuilderError . invalidValue ( " testProperty " , " must be positive " )
147+ XCTAssertEqual ( invalid. localizedDescription, " Invalid value for 'testProperty': must be positive " )
148+ }
149+
150+ func testBuilderFluentAPI( ) throws {
151+ // Given
152+ struct ComplexObject {
153+ let name : String
154+ let age : Int
155+ let email : String ?
156+ let tags : [ String ]
157+ }
158+
159+ class ComplexObjectBuilder : BaseBuilder < ComplexObject > {
160+ private var name : String ?
161+ private var age : Int ?
162+ private var email : String ?
163+ private var tags : [ String ] = [ ]
164+
165+ func setName( _ name: String ) -> Self {
166+ self . name = name
167+ return self
168+ }
169+
170+ func setAge( _ age: Int ) -> Self {
171+ self . age = age
172+ return self
173+ }
174+
175+ func setEmail( _ email: String ? ) -> Self {
176+ self . email = email
177+ return self
178+ }
179+
180+ func addTag( _ tag: String ) -> Self {
181+ self . tags. append ( tag)
182+ return self
183+ }
184+
185+ override func build( ) throws -> ComplexObject {
186+ guard let name = name else {
187+ throw BuilderError . missingRequiredProperty ( " name " )
188+ }
189+ guard let age = age else {
190+ throw BuilderError . missingRequiredProperty ( " age " )
191+ }
192+ return ComplexObject ( name: name, age: age, email: email, tags: tags)
193+ }
194+ }
195+
196+ // When - Test fluent API chaining
197+ let object = try ComplexObjectBuilder ( )
198+ . setName ( " John Doe " )
199+ . setAge ( 30 )
200+ . setEmail ( " john@example.com " )
201+ . addTag ( " developer " )
202+ . addTag ( " swift " )
203+ . build ( )
204+
205+ // Then
206+ XCTAssertEqual ( object. name, " John Doe " )
207+ XCTAssertEqual ( object. age, 30 )
208+ XCTAssertEqual ( object. email, " john@example.com " )
209+ XCTAssertEqual ( object. tags, [ " developer " , " swift " ] )
210+ }
211+
212+ func testBuilderWithOptionalProperties( ) throws {
213+ // Given
214+ struct OptionalObject {
215+ let required : String
216+ let optional : String ?
217+ }
218+
219+ class OptionalObjectBuilder : BaseBuilder < OptionalObject > {
220+ private var required : String ?
221+ private var optional : String ?
222+
223+ func setRequired( _ value: String ) -> Self {
224+ self . required = value
225+ return self
226+ }
227+
228+ func setOptional( _ value: String ? ) -> Self {
229+ self . optional = value
230+ return self
231+ }
232+
233+ override func build( ) throws -> OptionalObject {
234+ guard let required = required else {
235+ throw BuilderError . missingRequiredProperty ( " required " )
236+ }
237+ return OptionalObject ( required: required, optional: optional)
238+ }
239+ }
240+
241+ // When/Then - With optional value
242+ let object1 = try OptionalObjectBuilder ( )
243+ . setRequired ( " required " )
244+ . setOptional ( " optional " )
245+ . build ( )
246+ XCTAssertEqual ( object1. required, " required " )
247+ XCTAssertEqual ( object1. optional, " optional " )
248+
249+ // When/Then - Without optional value
250+ let object2 = try OptionalObjectBuilder ( )
251+ . setRequired ( " required " )
252+ . setOptional ( nil )
253+ . build ( )
254+ XCTAssertEqual ( object2. required, " required " )
255+ XCTAssertNil ( object2. optional)
256+ }
257+
258+ func testBuilderMultipleInstances( ) throws {
259+ // Given
260+ struct SimpleObject {
261+ let value : String
262+ }
263+
264+ class SimpleBuilder : BaseBuilder < SimpleObject > {
265+ private var value : String ?
266+
267+ func setValue( _ value: String ) -> Self {
268+ self . value = value
269+ return self
270+ }
271+
272+ override func build( ) throws -> SimpleObject {
273+ guard let value = value else {
274+ throw BuilderError . missingRequiredProperty ( " value " )
275+ }
276+ return SimpleObject ( value: value)
277+ }
278+ }
279+
280+ // When - Create multiple instances
281+ let builder1 = SimpleBuilder ( )
282+ let builder2 = SimpleBuilder ( )
283+
284+ let object1 = try builder1. setValue ( " value1 " ) . build ( )
285+ let object2 = try builder2. setValue ( " value2 " ) . build ( )
286+
287+ // Then - Each builder should be independent
288+ XCTAssertEqual ( object1. value, " value1 " )
289+ XCTAssertEqual ( object2. value, " value2 " )
290+ }
291+
292+ func testValidatingBuilderProtocolDefault( ) throws {
293+ // Given
294+ struct TestObject {
295+ let value : Int
296+ }
297+
298+ class DefaultValidatingBuilder : BaseBuilder < TestObject > , ValidatingBuilderProtocol {
299+ private var value : Int ?
300+
301+ func setValue( _ value: Int ) -> Self {
302+ self . value = value
303+ return self
304+ }
305+
306+ override func build( ) throws -> TestObject {
307+ guard let value = value else {
308+ throw BuilderError . missingRequiredProperty ( " value " )
309+ }
310+ return TestObject ( value: value)
311+ }
312+ }
313+
314+ // When/Then - Default validation should not throw
315+ let object = try DefaultValidatingBuilder ( )
316+ . setValue ( 42 )
317+ . build ( )
318+ XCTAssertEqual ( object. value, 42 )
319+ }
320+
321+ func testValidatingBuilderWithCustomValidation( ) throws {
322+ // Given
323+ struct User {
324+ let username : String
325+ let age : Int
326+ }
327+
328+ class UserBuilder : BaseBuilder < User > , ValidatingBuilderProtocol {
329+ private var username : String ?
330+ private var age : Int ?
331+
332+ func setUsername( _ username: String ) -> Self {
333+ self . username = username
334+ return self
335+ }
336+
337+ func setAge( _ age: Int ) -> Self {
338+ self . age = age
339+ return self
340+ }
341+
342+ func validate( ) throws {
343+ guard let username = username else {
344+ throw BuilderError . missingRequiredProperty ( " username " )
345+ }
346+ if username. count < 3 {
347+ throw BuilderError . invalidValue ( " username " , " must be at least 3 characters " )
348+ }
349+
350+ guard let age = age else {
351+ throw BuilderError . missingRequiredProperty ( " age " )
352+ }
353+ if age < 0 {
354+ throw BuilderError . invalidValue ( " age " , " must be non-negative " )
355+ }
356+ if age > 150 {
357+ throw BuilderError . invalidValue ( " age " , " must be less than 150 " )
358+ }
359+ }
360+
361+ override func build( ) throws -> User {
362+ try validate ( )
363+ return User ( username: username!, age: age!)
364+ }
365+ }
366+
367+ // When/Then - Valid user
368+ let validUser = try UserBuilder ( )
369+ . setUsername ( " johndoe " )
370+ . setAge ( 30 )
371+ . build ( )
372+ XCTAssertEqual ( validUser. username, " johndoe " )
373+ XCTAssertEqual ( validUser. age, 30 )
374+
375+ // When/Then - Invalid username (too short)
376+ XCTAssertThrowsError ( try UserBuilder ( ) . setUsername ( " ab " ) . setAge ( 30 ) . build ( ) ) { error in
377+ if case BuilderError . invalidValue( let property, _) = error {
378+ XCTAssertEqual ( property, " username " )
379+ } else {
380+ XCTFail ( " Expected invalidValue error for username " )
381+ }
382+ }
383+
384+ // When/Then - Invalid age (negative)
385+ XCTAssertThrowsError ( try UserBuilder ( ) . setUsername ( " johndoe " ) . setAge ( - 1 ) . build ( ) ) { error in
386+ if case BuilderError . invalidValue( let property, _) = error {
387+ XCTAssertEqual ( property, " age " )
388+ } else {
389+ XCTFail ( " Expected invalidValue error for age " )
390+ }
391+ }
392+
393+ // When/Then - Invalid age (too high)
394+ XCTAssertThrowsError ( try UserBuilder ( ) . setUsername ( " johndoe " ) . setAge ( 200 ) . build ( ) ) { error in
395+ if case BuilderError . invalidValue( let property, _) = error {
396+ XCTAssertEqual ( property, " age " )
397+ } else {
398+ XCTFail ( " Expected invalidValue error for age " )
399+ }
400+ }
401+ }
402+
403+ func testBuilderErrorEquality( ) {
404+ // Test that error cases can be matched
405+ let error1 = BuilderError . missingRequiredProperty ( " test " )
406+ let error2 = BuilderError . missingRequiredProperty ( " test " )
407+
408+ // Use pattern matching to verify
409+ if case BuilderError . missingRequiredProperty( let prop1) = error1,
410+ case BuilderError . missingRequiredProperty( let prop2) = error2 {
411+ XCTAssertEqual ( prop1, prop2)
412+ } else {
413+ XCTFail ( " Error pattern matching failed " )
414+ }
415+ }
416+
417+ func testBuilderReuse( ) throws {
418+ // Given
419+ struct Config {
420+ let host : String
421+ let port : Int
422+ }
423+
424+ class ConfigBuilder : BaseBuilder < Config > {
425+ private var host : String ?
426+ private var port : Int ?
427+
428+ func setHost( _ host: String ) -> Self {
429+ self . host = host
430+ return self
431+ }
432+
433+ func setPort( _ port: Int ) -> Self {
434+ self . port = port
435+ return self
436+ }
437+
438+ override func build( ) throws -> Config {
439+ guard let host = host else {
440+ throw BuilderError . missingRequiredProperty ( " host " )
441+ }
442+ guard let port = port else {
443+ throw BuilderError . missingRequiredProperty ( " port " )
444+ }
445+ return Config ( host: host, port: port)
446+ }
447+ }
448+
449+ // When - Reuse builder instance
450+ let builder = ConfigBuilder ( )
451+
452+ let config1 = try builder
453+ . setHost ( " localhost " )
454+ . setPort ( 8080 )
455+ . build ( )
456+
457+ // Reset and build again
458+ let config2 = try builder
459+ . setHost ( " example.com " )
460+ . setPort ( 443 )
461+ . build ( )
462+
463+ // Then - Last values should be used
464+ XCTAssertEqual ( config1. host, " localhost " )
465+ XCTAssertEqual ( config1. port, 8080 )
466+ XCTAssertEqual ( config2. host, " example.com " )
467+ XCTAssertEqual ( config2. port, 443 )
468+ }
115469}
116470
0 commit comments