blob: 1999eb478b57111aab909d6cbb98d412d4c82a40 [file]
/**
* Copyright (c) 2024 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MTREndpointInfo_Test.h"
#import <Matter/Matter.h>
#import <XCTest/XCTest.h>
@interface MTREndpointInfoTests : XCTestCase
@end
@implementation MTREndpointInfoTests
static MTREndpointInfo * MakeEndpoint(NSNumber * endpointID, NSArray<NSNumber *> * parts)
{
return [[MTREndpointInfo alloc] initWithEndpointID:endpointID deviceTypes:@[] partsList:parts];
}
static NSArray<NSNumber *> * ChildEndpointIDs(MTREndpointInfo * endpoint)
{
return [[endpoint.children valueForKey:@"endpointID"] sortedArrayUsingSelector:@selector(compare:)];
}
static NSArray<NSNumber *> * Exclude(NSArray<NSNumber *> * numbers, NSNumber * numberToExclude)
{
NSMutableArray * result = [numbers mutableCopy];
[result removeObject:numberToExclude];
return result;
}
- (NSDictionary<NSNumber *, MTREndpointInfo *> *)indexEndpoints:(NSArray<MTREndpointInfo *> *)endpoints
{
NSMutableDictionary * indexed = [[NSMutableDictionary alloc] init];
for (MTREndpointInfo * endpoint in endpoints) {
indexed[endpoint.endpointID] = endpoint;
}
XCTAssertEqual(indexed.count, endpoints.count, @"Duplicate endpoint IDs");
return indexed;
}
- (void)testPopulateChildren
{
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern
MakeEndpoint(@1, @[ @2, @3 ]),
MakeEndpoint(@2, @[]),
MakeEndpoint(@3, @[]),
MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern
MakeEndpoint(@5, @[ @6 ]),
MakeEndpoint(@6, @[]),
]];
XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]);
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[ @1, @4 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@1]), (@[ @2, @3 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@2]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@3]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@4]), (@[ @5 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@5]), (@[ @6 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@6]), (@[]));
}
- (void)testPopulateChildren2
{
// Same as testPopulateChildren, but with reversed PartsLists
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[ @6, @5, @4, @3, @2, @1 ]), // full-family pattern
MakeEndpoint(@1, @[ @3, @2 ]),
MakeEndpoint(@2, @[]),
MakeEndpoint(@3, @[]),
MakeEndpoint(@4, @[ @6, @5 ]), // full-family pattern
MakeEndpoint(@5, @[ @6 ]),
MakeEndpoint(@6, @[]),
]];
XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]);
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[ @1, @4 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@1]), (@[ @2, @3 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@2]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@3]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@4]), (@[ @5 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@5]), (@[ @6 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@6]), (@[]));
}
- (void)testPopulateChildrenRootOnly
{
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[]),
]];
XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]);
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[]));
}
- (void)testPopulateChildrenInvalidCompositionCycle
{
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern
MakeEndpoint(@1, @[ @2, @3 ]),
MakeEndpoint(@2, @[]),
MakeEndpoint(@3, @[]),
MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern
MakeEndpoint(@5, @[ @6 ]),
MakeEndpoint(@6, @[ @4 ]), // not valid per spec: cycle 4 -> 5 -> 6 -> 4
]];
XCTAssertFalse([MTREndpointInfo populateChildrenForEndpoints:endpoints]);
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[ @1, @4 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@1]), (@[ @2, @3 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@2]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@3]), (@[]));
// We make no promises about child lists for endpoints involved in a cycle
}
- (void)testPopulateChildrenInvalidNonTree
{
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern
MakeEndpoint(@1, @[ @2, @3, @6 ]),
MakeEndpoint(@2, @[]),
MakeEndpoint(@3, @[]),
MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern
MakeEndpoint(@5, @[ @6 ]),
MakeEndpoint(@6, @[]), // not valid per spec: 6 is a child of both 1 and 5
]];
// Note: Not asserting a false return value here, this scenario is currently not detected.
[MTREndpointInfo populateChildrenForEndpoints:endpoints];
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[ @1, @4 ]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@2]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@3]), (@[]));
XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@4]), (@[ @5 ]));
// Endpoint 6 has multiple parents, so we make no guarantees where (or if) it shows up
XCTAssertEqualObjects(Exclude(ChildEndpointIDs(endpoints[@1]), @6), (@[ @2, @3 ]));
XCTAssertEqualObjects(Exclude(ChildEndpointIDs(endpoints[@5]), @6), (@[]));
}
- (void)testEqualityAndCopying
{
MTRDeviceTypeRevision * doorLock = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@0x0A revision:@1];
MTRDeviceTypeRevision * rootNode = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@0x16 revision:@1];
MTREndpointInfo * a1 = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ rootNode ] partsList:@[]];
XCTAssertTrue([a1 isEqual:a1]);
XCTAssertTrue([a1 isEqual:[a1 copy]]);
XCTAssertFalse([a1 isEqual:nil]);
XCTAssertFalse([a1 isEqual:@"hello"]);
MTREndpointInfo * a2 = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ rootNode ] partsList:@[]];
XCTAssertTrue([a1 isEqual:a2]);
XCTAssertTrue([a2 isEqual:a1]);
XCTAssertEqual(a1.hash, a2.hash);
MTREndpointInfo * b = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ rootNode ] partsList:@[ @2 ]];
XCTAssertFalse([a1 isEqual:b]);
XCTAssertFalse([b isEqual:a1]);
MTREndpointInfo * c = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ doorLock ] partsList:@[]];
XCTAssertFalse([a1 isEqual:c]);
XCTAssertFalse([c isEqual:a1]);
MTREndpointInfo * d = [[MTREndpointInfo alloc] initWithEndpointID:@2 deviceTypes:@[ rootNode ] partsList:@[]];
XCTAssertFalse([a1 isEqual:d]);
XCTAssertFalse([d isEqual:a1]);
}
- (void)testSecureCoding
{
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern
MakeEndpoint(@1, @[ @2, @3 ]),
MakeEndpoint(@2, @[]),
MakeEndpoint(@3, @[]),
MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern
MakeEndpoint(@5, @[ @6 ]),
MakeEndpoint(@6, @[]),
]];
XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]);
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:endpoints.allValues requiringSecureCoding:YES error:NULL];
NSArray<MTREndpointInfo *> * decodedEndpoints = [NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:MTREndpointInfo.class fromData:data error:NULL];
XCTAssertNotNil(decodedEndpoints);
XCTAssertEqualObjects(decodedEndpoints, endpoints.allValues);
// Deeply compare by hand as well, `children` is not checked by isEqual:
[decodedEndpoints enumerateObjectsUsingBlock:^(MTREndpointInfo * decoded, NSUInteger idx, BOOL * stop) {
MTREndpointInfo * original = endpoints.allValues[idx];
XCTAssertTrue([decoded isEqual:original]);
XCTAssertEqualObjects(decoded.endpointID, original.endpointID);
XCTAssertEqualObjects(decoded.deviceTypes, original.deviceTypes);
XCTAssertEqualObjects(decoded.partsList, original.partsList);
XCTAssertEqualObjects(decoded.children, original.children);
}];
}
@end