public void Load(BDictionary data) { UserName = data.Get <BString>("uname").Value.ToString(); Country = data.Get <BString>("uctry").Value.ToString(); TimeZone = data.Get <BString>("utz").Value.ToString(); Languages = data.Get <BString>("ulangs").Value.ToString(); }
private void OnRecvAnnouncePeerQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var infoHash = new DHTId(args.Get <BString>("info_hash")); var impliedPort = args.Get <BNumber>("implied_port"); int port = (impliedPort != null && impliedPort.Value == 1) ? ipinfo.Port : (int)args.Get <BNumber>("port").Value; var remoteNode = new DHTNode(id.Value, ipinfo); UpdateRoutingTable(remoteNode); #if DEBUG_DHT_INTERNALS fLogger.WriteDebug("Receive `announce_peer` query from {0} for {1}", remoteNode.ToString(), infoHash.ToString()); #endif // skip response for another infohash query if (infoHash == fSearchInfoHash) { // receive `announce_peer` query for our infohash var nodesList = fRoutingTable.GetClosest(infoHash.Data); Send(ipinfo, DHTMessage.CreateAnnouncePeerResponse(t, fLocalID, nodesList)); if (PeersFound != null) { PeersFound(this, new PeersFoundEventArgs(new List <IPEndPoint>() { ipinfo })); } } }
/// <summary> /// Parses trackers (announce URLs) from a torrent. /// </summary> /// <param name="data">The torrent data to parse trackers from.</param> /// <param name="encoding"></param> /// <returns>A list of list of trackers (announce URLs).</returns> protected virtual IList <IList <string> > ParseTrackers(BDictionary data, Encoding encoding) { var trackerList = new List <IList <string> >(); var primary = new List <string>(); trackerList.Add(primary); // Get single 'announce' url and add it to the primary list if there is any var announce = data.Get <BString>(TorrentFields.Announce)?.ToString(encoding); if (!string.IsNullOrEmpty(announce)) { primary.Add(announce); } // Get the 'announce-list' list´s var announceLists = data.Get <BList>(TorrentFields.AnnounceList)?.AsType <BList>() as IList <BList>; if (announceLists?.Any() == true) { // Add the first list to the primary list and remove duplicates primary.AddRange(announceLists.First().AsStrings(encoding)); trackerList[0] = primary.Distinct().ToList(); // Add the other lists to the lists of lists of announce urls trackerList.AddRange( announceLists.Skip(1) .Select(x => x.AsStrings(encoding).ToList())); } return(trackerList); }
private void OnRecvAnnouncePeerQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var infoHash = args.Get <BString>("info_hash"); var impliedPort = args.Get <BNumber>("implied_port"); int port = (impliedPort != null && impliedPort.Value == 1) ? ipinfo.Port : (int)args.Get <BNumber>("port").Value; fLogger.WriteDebug("Receive `announce_peer` query from {0} [{1}] for {2}", ipinfo.ToString(), id.Value.ToHexString(), infoHash.Value.ToHexString()); fRoutingTable.UpdateNode(new DHTNode(id.Value, ipinfo)); if (!Algorithms.ArraysEqual(infoHash.Value, fSearchInfoHash)) { // skip response for another infohash query return; } // receive `announce_peer` query for our infohash var nodesList = fRoutingTable.FindNodes(infoHash.Value); Send(ipinfo, DHTMessage.CreateAnnouncePeerResponse(t, fLocalID, nodesList)); }
/// <summary> /// Parses file info for multi-file torrents. /// </summary> /// <param name="info">The 'info'-dictionary of a torrent.</param> /// <param name="encoding"></param> /// <returns>The file info.</returns> protected virtual MultiFileInfoList ParseMultiFileInfo(BDictionary info, Encoding encoding) { if (!info.ContainsKey(TorrentInfoFields.Files)) { return(null); } var list = new MultiFileInfoList { DirectoryName = info.Get <BString>(TorrentInfoFields.Name)?.ToString(encoding), DirectoryNameUtf8 = info.Get <BString>(TorrentInfoFields.NameUtf8)?.ToString(encoding) }; var fileInfos = info.Get <BList>(TorrentInfoFields.Files).Cast <BDictionary>() .Select(x => new MultiFileInfo { FileSize = x.Get <BNumber>(TorrentFilesFields.Length), Path = x.Get <BList>(TorrentFilesFields.Path)?.AsStrings(encoding).ToList(), PathUtf8 = x.Get <BList>(TorrentFilesFields.PathUtf8)?.AsStrings(Encoding.UTF8).ToList(), Md5Sum = x.Get <BString>(TorrentFilesFields.Md5Sum)?.ToString(encoding) }); list.AddRange(fileInfos); return(list); }
private void OnRecvResponseX(IPEndPoint ipinfo, BDictionary data) { var tid = data.Get <BString>("t"); var returnValues = data.Get <BDictionary>("r"); var id = returnValues.Get <BString>("id"); var tokStr = returnValues.Get <BString>("token"); var valuesList = returnValues.Get <BList>("values"); var nodesStr = returnValues.Get <BString>("nodes"); if (id == null || id.Length == 0) { // response is invalid return; } fRoutingTable.UpdateNode(new DHTNode(id.Value, ipinfo)); // according to bep_????, most types of response contain a list of nodes ProcessNodesStr(ipinfo, nodesStr); // define type of response by transactionId of query/response QueryType queryType = CheckTransaction(tid); bool canAnnounce = false; switch (queryType) { case QueryType.ping: RaisePeerPinged(ipinfo, id.Value); break; case QueryType.find_node: break; case QueryType.get_peers: if (tokStr != null && tokStr.Length != 0) { if (!ProcessValuesStr(ipinfo, valuesList)) { canAnnounce = true; } } break; case QueryType.announce_peer: break; case QueryType.none: // TransactionId bad or unknown RaiseResponseReceived(ipinfo, id.Value, data); break; } if (canAnnounce) { SendAnnouncePeerQuery(ipinfo, fSearchInfoHash, 1, fLocalIP.Port, tokStr); } }
private void OnRecvResponseX(IPEndPoint ipinfo, DHTResponseMessage msg) { if (msg == null) { return; } BDictionary data = msg.Data; var tid = data.Get <BString>("t"); var returnValues = data.Get <BDictionary>("r"); if (returnValues == null) { // response is invalid return; } BString id = returnValues.Get <BString>("id"); BString tokStr = returnValues.Get <BString>("token"); BList valuesList = returnValues.Get <BList>("values"); BString nodesStr = returnValues.Get <BString>("nodes"); var remoteNode = new DHTNode(id.Value, ipinfo); UpdateRoutingTable(remoteNode); // define type of response by transactionId of query/response QueryType queryType = fTransactions.CheckQuery(tid); switch (queryType) { case QueryType.Ping: OnRecvPingResponse(remoteNode); break; case QueryType.FindNode: OnRecvFindNodeResponse(remoteNode, nodesStr); break; case QueryType.GetPeers: OnRecvGetPeersResponse(remoteNode, nodesStr, valuesList, tokStr); break; case QueryType.AnnouncePeer: OnRecvAnnouncePeerResponse(remoteNode); break; case QueryType.None: // TransactionId bad or unknown OnRecvCustomResponse(remoteNode, data); break; } }
public void Load(BDictionary data) { if (data == null) { throw new ArgumentNullException("data"); } UserName = data.Get <BString>("uname").ToString(); Country = data.Get <BString>("uctry").ToString(); TimeZone = data.Get <BString>("utz").ToString(); Languages = data.Get <BString>("ulangs").ToString(); }
private void OnRecvPingQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); fLogger.WriteDebug("Receive `ping` query from {0} [{1}]", ipinfo.ToString(), id.Value.ToHexString()); fRoutingTable.UpdateNode(new DHTNode(id.Value, ipinfo)); Send(ipinfo, DHTMessage.CreatePingResponse(t, fLocalID)); }
/// <summary> /// Parses file info for single-file torrents. /// </summary> /// <param name="info">The 'info'-dictionary of a torrent.</param> /// <param name="encoding"></param> /// <returns>The file info.</returns> protected virtual SingleFileInfo ParseSingleFileInfo(BDictionary info, Encoding encoding) { if (!info.ContainsKey(TorrentInfoFields.Length)) { return(null); } return(new SingleFileInfo { FileName = info.Get <BString>(TorrentInfoFields.Name)?.ToString(encoding), FileSize = info.Get <BNumber>(TorrentInfoFields.Length), Md5Sum = info.Get <BString>(TorrentInfoFields.Md5Sum)?.ToString(encoding) }); }
/// <summary> /// Parses trackers (announce URLs) from a torrent. /// </summary> /// <param name="data">The torrent data to parse trackers from.</param> /// <param name="encoding"></param> /// <returns>A list of list of trackers (announce URLs).</returns> protected virtual IList <IList <string> > ParseTrackers(BDictionary data, Encoding encoding) { // Specification: http://bittorrent.org/beps/bep_0012.html var trackerList = new List <IList <string> >(); var primary = new List <string>(); trackerList.Add(primary); // Get single 'announce' url and add it to the primary list if there is any var announce = data.Get <BString>(TorrentFields.Announce)?.ToString(encoding); if (!string.IsNullOrEmpty(announce)) { primary.Add(announce); } // Get the 'announce-list' list's var announceLists = data.Get <BList>(TorrentFields.AnnounceList); if (announceLists == null) { return(trackerList); } // According to the specification it should be a list of lists if (announceLists.All(x => x is BList)) { var lists = announceLists.AsType <BList>() as IList <BList>; if (lists.Any()) { // Add the first list to the primary list and remove duplicates primary.AddRange(lists.First().AsStrings(encoding)); trackerList[0] = primary.Distinct().ToList(); // Add the other lists to the lists of lists of announce urls trackerList.AddRange(lists.Skip(1).Select(x => x.AsStrings(encoding).ToList())); } } // It's not following the specification, it's strings instead of lists else if (ParseMode == TorrentParserMode.Tolerant && announceLists.All(x => x is BString)) { // Add them all to the first list primary.AddRange(announceLists.AsStrings(encoding)); trackerList[0] = primary.Distinct().ToList(); } return(trackerList); }
public void SingleFileInfo_IsParsed(long length, string fileName, string fileNameUtf8, string md5Sum) { // Arrange ParsedData = ValidSingleFileTorrentData; var info = ParsedData.Get <BDictionary>(TorrentFields.Info); info[TorrentInfoFields.Length] = (BNumber)length; info[TorrentInfoFields.Name] = (BString)fileName; info[TorrentInfoFields.NameUtf8] = (BString)fileNameUtf8; info[TorrentInfoFields.Md5Sum] = (BString)md5Sum; // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeReader)null); // Assert torrent.Files.Should().BeNull(); torrent.FileMode.Should().Be(TorrentFileMode.Single); torrent.TotalSize.Should().Be(length); torrent.File.Should().NotBeNull(); torrent.File.FileSize.Should().Be(length); torrent.File.FileName.Should().Be(fileName); torrent.File.FileNameUtf8.Should().Be(fileNameUtf8); torrent.File.Md5Sum.Should().Be(md5Sum); }
private void OnRecvFindNodeQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var target = args.Get <BString>("target"); fLogger.WriteDebug("Receive `find_node` query from {0} [{1}]", ipinfo.ToString(), id.Value.ToHexString()); fRoutingTable.UpdateNode(new DHTNode(id.Value, ipinfo)); var nodesList = fRoutingTable.FindNodes(target.Value); Send(ipinfo, DHTMessage.CreateFindNodeResponse(t, fLocalID, nodesList)); }
public void Add_NullStringValue_ResultsInEmptyBString() { var dict = new BDictionary { { "key", (string)null } }; dict.Get <BString>("key").Value.ToArray().Should().BeEmpty(); }
private void OnRecvPingQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var remoteNode = new DHTNode(id.Value, ipinfo); UpdateRoutingTable(remoteNode); #if DEBUG_DHT_INTERNALS fLogger.WriteDebug("Receive `ping` query from {0}", remoteNode.ToString()); #endif Send(ipinfo, DHTMessage.CreatePingResponse(t, fLocalID)); }
private void OnRecvGetPeersQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var infoHash = args.Get <BString>("info_hash"); fLogger.WriteDebug("Receive `get_peers` query from {0} [{1}] for {2}", ipinfo.ToString(), id.Value.ToHexString(), infoHash.Value.ToHexString()); fRoutingTable.UpdateNode(new DHTNode(id.Value, ipinfo)); var neighbor = DHTHelper.GetNeighbor(infoHash.Value, fLocalID); var peersList = (Algorithms.ArraysEqual(infoHash.Value, fSearchInfoHash)) ? fPeersHolder.GetPeersList() : null; var nodesList = fRoutingTable.FindNodes(infoHash.Value); Send(ipinfo, DHTMessage.CreateGetPeersResponse(t, neighbor, infoHash.Value, peersList, nodesList)); }
public void RequestScrape(NetworkManager network) { if (IsScrapeUpdated) { return; } try { string scrapeUrl = File.GenerateScrapeUrlString(Client.HashUpperCase); if (string.IsNullOrEmpty(scrapeUrl)) { log.Info("This tracker doesnt seem to support scrape"); } Uri url = new Uri(scrapeUrl); Tracker = network.MakeWebRequest(url, Client.HttpProtocol, Client.Headers); if (Tracker != null && Tracker.Dico != null) { BDictionary dico = Tracker.Dico; if (dico["failure reason"] != null) { log.Error($"Tracker Error : {dico["failure reason"]}"); return; } log.Info("---------- Scrape Info -----------"); BDictionary dicoFiles = dico.Get <BDictionary>("files"); string infoHash = Encoding.GetEncoding(0x4e4).GetString(File.InfoHashBytes); BDictionary dicoHashes = dicoFiles.Get <BDictionary>(infoHash); if (dicoHashes != null) { Seeders = -1; Leechers = -1; log.Info("complete: " + dicoHashes["complete"]); log.Info("downloaded: " + dicoHashes["downloaded"]); log.Info("incomplete: " + dicoHashes["incomplete"]); int parseInt; if (int.TryParse(dicoHashes["complete"].ToString(), out parseInt)) { Seeders = parseInt; } if (int.TryParse(dicoHashes["incomplete"].ToString(), out parseInt)) { Leechers = parseInt; } IsScrapeUpdated = true; } else { log.Info($"Scrape returned : '{dicoFiles[infoHash]}'"); } } } catch (Exception ex) { log.Error(ex, "RequestScrape Error"); } }
private void OnRecvFindNodeQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var target = args.Get <BString>("target"); var remoteNode = new DHTNode(id.Value, ipinfo); UpdateRoutingTable(remoteNode); #if DEBUG_DHT_INTERNALS fLogger.WriteDebug("Receive `find_node` query from {0}", remoteNode.ToString()); #endif var nodesList = fRoutingTable.GetClosest(target.Value); Send(ipinfo, DHTMessage.CreateFindNodeResponse(t, fLocalID, nodesList)); }
public void MergeWith_DictionaryWithNewKeyIsAdded() { var dict1 = new BDictionary { { "main", new BDictionary { { "key", "value" } } } }; var dict2 = new BDictionary { { "main2", new BDictionary { { "key2", "value2" } } } }; dict1.MergeWith(dict2); dict1.Should().HaveCount(2); dict1.Get <BDictionary>("main").Should().HaveCount(1).And.ContainKeys("key"); dict1.Get <BDictionary>("main2").Should().HaveCount(1).And.ContainKeys("key2"); }
private void OnRecvPingQuery(IPEndPoint ipinfo, BDictionary data, bool similar) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); fLogger.WriteDebug("Receive `ping` query from {0} [{1}]", ipinfo.ToString(), id.Value.ToHexString()); fRoutingTable.UpdateNode(new DHTNode(id.Value, ipinfo)); // FIXME: temp debug code if (similar) { fLogger.WriteDebug(">>>>>>>>>>>> Found a similar peer! " + ipinfo.ToString()); } Send(ipinfo, DHTMessage.CreatePingResponse(t, fLocalID)); }
private void OnRecvErrorX(IPEndPoint ipinfo, BDictionary data) { var errData = data.Get <BList>("e"); if (errData != null && errData.Count != 0) { var errCode = errData.Get <BNumber>(0); var errText = errData.Get <BString>(1); fLogger.WriteDebug("DHT error receive: " + errCode + " / " + errText); } }
private void OnRecvGetPeersQuery(IPEndPoint ipinfo, BDictionary data) { var t = data.Get <BString>("t"); var args = data.Get <BDictionary>("a"); var id = args.Get <BString>("id"); var infoHash = new DHTId(args.Get <BString>("info_hash")); var remoteNode = new DHTNode(id.Value, ipinfo); UpdateRoutingTable(remoteNode); #if DEBUG_DHT_INTERNALS fLogger.WriteDebug("Receive `get_peers` query from {0} for {1}", remoteNode.ToString(), infoHash.ToString()); #endif var neighbor = DHTNode.GetNeighbor(infoHash.Data, fLocalID.Data); var peersList = (infoHash == fSearchInfoHash) ? fPeersHolder.GetPeersList() : null; var nodesList = fRoutingTable.GetClosest(infoHash.Data); Send(ipinfo, DHTMessage.CreateGetPeersResponse(t, neighbor, infoHash, peersList, nodesList)); }
/// <summary> /// Creates a torrent by reading the relevant data from the <see cref="BDictionary"/>. /// </summary> /// <param name="data">The torrent bencode data.</param> /// <returns>A <see cref="Torrent"/> matching the input.</returns> protected Torrent CreateTorrent(BDictionary data) { if (ParseMode == TorrentParserMode.Strict) { EnsureValidTorrentData(data); } var info = data.Get <BDictionary>(TorrentFields.Info); var encoding = ParseEncoding(data.Get <BString>(TorrentFields.Encoding)) ?? BencodeParser.Encoding; var torrent = new Torrent(info) { IsPrivate = info.Get <BNumber>(TorrentInfoFields.Private) == 1, PieceSize = info.Get <BNumber>(TorrentInfoFields.PieceLength), Pieces = info.Get <BString>(TorrentInfoFields.Pieces)?.Value.ToArray() ?? new byte[0], Comment = data.Get <BString>(TorrentFields.Comment)?.ToString(encoding), CreatedBy = data.Get <BString>(TorrentFields.CreatedBy)?.ToString(encoding), Encoding = ParseEncoding(data.Get <BString>(TorrentFields.Encoding)), CreationDate = data.Get <BNumber>(TorrentFields.CreationDate), File = ParseSingleFileInfo(info, encoding), Files = ParseMultiFileInfo(info, encoding), Trackers = ParseTrackers(data, encoding), ExtraFields = ParseAnyExtraFields(data, encoding) }; return(torrent); }
public void OriginalInfoHashBytes_IsSet() { // Arrange ParsedData = ValidSingleFileTorrentData; var expectedInfoHashBytes = TorrentUtil.CalculateInfoHashBytes(ParsedData.Get <BDictionary>("info")); // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeReader)null); // Assert torrent.OriginalInfoHashBytes.Should().Equal(expectedInfoHashBytes); }
public void OriginalInfoHash_IsSet() { // Arrange ParsedData = ValidSingleFileTorrentData; var expectedInfoHash = TorrentUtil.CalculateInfoHash(ParsedData.Get <BDictionary>("info")); // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeStream)null); // Assert torrent.OriginalInfoHash.Should().Be(expectedInfoHash); }
public void Info_PieceLength_IsParsed(long pieceSize) { // Arrange ParsedData = ValidSingleFileTorrentData; var info = ParsedData.Get <BDictionary>(TorrentFields.Info); info[TorrentInfoFields.PieceLength] = (BNumber)pieceSize; // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeReader)null); // Assert torrent.PieceSize.Should().Be(pieceSize); }
public void Info_Pieces_IsParsed(byte[] pieces) { // Arrange ParsedData = ValidSingleFileTorrentData; var info = ParsedData.Get <BDictionary>(TorrentFields.Info); info[TorrentInfoFields.Pieces] = new BString(pieces); // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeReader)null); // Assert torrent.Pieces.Should().Equal(pieces); }
public void Info_Private_ShouldBeTrueOnlyIfValueIsOne(int value, bool expectedResult) { // Arrange ParsedData = ValidSingleFileTorrentData; var info = ParsedData.Get <BDictionary>(TorrentFields.Info); info[TorrentInfoFields.Private] = (BNumber)value; // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeReader)null); // Assert torrent.IsPrivate.Should().Be(expectedResult); }
public void ExtraFields_IsParsed(string extraKey, string extraValue, string extraInfoKey, string extraInfoValue) { // Arrange ParsedData = ValidSingleFileTorrentData; ParsedData[extraKey] = (BString)extraValue; ParsedData.Get <BDictionary>(TorrentFields.Info)[extraInfoKey] = (BString)extraInfoValue; // Act var parser = new TorrentParser(BencodeParser); var torrent = parser.Parse((BencodeReader)null); // Assert torrent.ExtraFields.Should().Contain(extraKey, (BString)extraValue); torrent.ExtraFields.Get <BDictionary>(TorrentFields.Info).Should().Contain(extraInfoKey, (BString)extraInfoValue); }
/// <summary> /// Checks the torrent data for required fields and throws an exception if any are missing or invalid. /// </summary> /// <param name="data">The torrent data.</param> /// <exception cref="InvalidTorrentException">The torrent data is missing required fields or otherwise invalid.</exception> protected void EnsureValidTorrentData(BDictionary data) { /* * NOTE: The 'announce' field is technically required according to the specification, * but is not really required for DHT and PEX. */ if (!data.ContainsKey(TorrentFields.Info)) { throw new InvalidTorrentException("Torrent is missing 'info'-dictionary.", TorrentFields.Info); } var info = data.Get <BDictionary>(TorrentFields.Info); var requiredFields = new List <string> { TorrentInfoFields.PieceLength, TorrentInfoFields.Pieces, TorrentInfoFields.Name }; // Single-file torrents must have either the 'length' field or the 'files' field, but not both if (info.ContainsKey(TorrentInfoFields.Length) && info.ContainsKey(TorrentInfoFields.Files)) { throw new InvalidTorrentException( $"Torrent 'info'-dictionary cannot contain both '{TorrentInfoFields.Length}' and '{TorrentInfoFields.Files}'."); } if (!info.ContainsKey(TorrentInfoFields.Length)) { requiredFields.Add(TorrentInfoFields.Files); } EnsureFields(requiredFields, info, "Torrent is missing required field in 'info'-dictionary."); if (info.ContainsKey(TorrentInfoFields.Files)) { var filesData = info.Get <BList>(TorrentInfoFields.Files).AsType <BDictionary>(); var requiredFileFields = new[] { TorrentFilesFields.Length, TorrentFilesFields.Path }; EnsureFields(requiredFileFields, filesData, "Torrent is missing required field in 'info.files' dictionaries."); } }
public void MergeWith_ExistingKeyOption_Skip_ListIsSkippedForExistingKeyOfSameType() { var dict1 = new BDictionary {{"key", new BList {"item1"}}}; var dict2 = new BDictionary {{"key", new BList {"item2", "item3"}}}; dict1.MergeWith(dict2, ExistingKeyAction.Skip); dict1.Should().HaveCount(1); dict1["key"].Should().BeOfType<BList>(); dict1.Get<BList>("key").Should() .HaveCount(1) .And.ContainInOrder((BString) "item1"); }
public void MergeWith_ExistingKeyOption_Skip_DictionarySkippedForExistingKeyOfSameType() { var dict1 = new BDictionary {{"main", new BDictionary {{"key", "value"}}}}; var dict2 = new BDictionary {{"main", new BDictionary {{"key2", "value2"}}}}; dict1.MergeWith(dict2, ExistingKeyAction.Skip); dict1.Should().HaveCount(1); dict1.Get<BDictionary>("main").Should().HaveCount(1).And.ContainKey("key"); }
public void MergeWith_ExistingKeyOption_Merge_DictionaryMergedWithExistingKeyOfSameType() { var dict1 = new BDictionary {{"main", new BDictionary {{"key", "value"}}}}; var dict2 = new BDictionary {{"main", new BDictionary {{"key2", "value2"}}}}; dict1.MergeWith(dict2, ExistingKeyAction.Merge); dict1.Should().HaveCount(1); dict1.Get<BDictionary>("main").Should().HaveCount(2).And.ContainKeys("key", "key2"); }
public void MergeWith_DictionaryWithNewKeyIsAdded() { var dict1 = new BDictionary { { "main", new BDictionary { { "key", "value" } } } }; var dict2 = new BDictionary { { "main2", new BDictionary { { "key2", "value2" } } } }; dict1.MergeWith(dict2); dict1.Should().HaveCount(2); dict1.Get<BDictionary>("main").Should().HaveCount(1).And.ContainKeys("key"); dict1.Get<BDictionary>("main2").Should().HaveCount(1).And.ContainKeys("key2"); }
public void MergeWith_DictionaryReplacesExistingKeyOfDifferentType() { var dict1 = new BDictionary {{"main", new BList {"item1"}}}; var dict2 = new BDictionary {{"main", new BDictionary {{"key", "value"}}}}; dict1.MergeWith(dict2); dict1.Should().HaveCount(1); dict1.Get<BDictionary>("main").Should().HaveCount(1).And.ContainKey("key"); }
public void MergeWith_ListReplacesExistingKeyOfDifferentType() { var dict1 = new BDictionary {{"key", "value"}}; var dict2 = new BDictionary {{"key", new BList {"item1", "item2"}}}; dict1.MergeWith(dict2); dict1.Should().HaveCount(1); dict1["key"].Should().BeOfType<BList>(); dict1.Get<BList>("key").Should() .HaveCount(2) .And.ContainInOrder((BString) "item1", (BString) "item2"); }