public void ImportAsReplace_TopicDataWithRelationships_DeletesOrphanedRelationships() {

      var rootTopic             = TopicFactory.Create("Root", "Container");
      var topic                 = TopicFactory.Create("Test", "Container", rootTopic);
      var relatedTopic1         = TopicFactory.Create("Related1", "Container", rootTopic);
      var relatedTopic2         = TopicFactory.Create("Related2", "Container", rootTopic);
      var relatedTopic3         = TopicFactory.Create("Related3", "Container", rootTopic);
      var topicData             = new TopicData() {
        Key                     = topic.Key,
        UniqueKey               = topic.GetUniqueKey(),
        ContentType             = topic.ContentType
      };
      var relationshipData      = new KeyValuesPair() {
        Key                   = "Related"
      };

      topic.Relationships.SetValue("Related", relatedTopic1);
      topic.Relationships.SetValue("Related", relatedTopic2);
      topic.Relationships.SetValue("Cousin",  relatedTopic3);

      topicData.Relationships.Add(relationshipData);
      relationshipData.Values.Add(relatedTopic1.GetUniqueKey());

      topic.Import(
        topicData,
        new() {
          Strategy              = ImportStrategy.Replace
        }
      );

      Assert.AreEqual(relatedTopic1, topic.Relationships.GetValues("Related")?.FirstOrDefault());
      Assert.AreEqual<int>(1, topic.Relationships.GetValues("Related").Count);
      Assert.AreEqual<int>(0, topic.Relationships.GetValues("Cousin").Count);

    }
    public void Import_TopicDataWithRelationships_MaintainsExisting() {

      var rootTopic             = TopicFactory.Create("Root", "Container");
      var topic                 = TopicFactory.Create("Test", "Container", rootTopic);
      var relatedTopic1         = TopicFactory.Create("Related1", "Container", rootTopic);
      var relatedTopic2         = TopicFactory.Create("Related2", "Container", rootTopic);
      var topicData             = new TopicData() {
        Key                     = topic.Key,
        UniqueKey               = topic.GetUniqueKey(),
        ContentType             = topic.ContentType
      };
      var relationshipData      = new KeyValuesPair() {
        Key                     = "Related"
      };

      topic.Relationships.SetValue("Related", relatedTopic1);

      topicData.Relationships.Add(relationshipData);
      relationshipData.Values.Add(relatedTopic2.GetUniqueKey());

      topic.Import(topicData);

      Assert.AreEqual(relatedTopic1, topic.Relationships.GetValues("Related")?.FirstOrDefault());
      Assert.AreEqual(relatedTopic2, topic.Relationships.GetValues("Related")?.LastOrDefault());

    }
    public void Serialize_KeyValuesPair_ReturnsExpectedResults() {

      var keyValuesPair         = new KeyValuesPair() {
        Key                     = "Test"
      };
      keyValuesPair.Values.Add("Root:Web");

      var expected = $"{{" +
        $"\"Key\":\"{keyValuesPair.Key}\"," +
        $"\"Values\":[\"Root:Web\"]" +
        $"}}";

      var json = JsonSerializer.Serialize(keyValuesPair);

      Assert.AreEqual<string>(expected, json);

    }
    public void Deserialize_RelationshipData_ReturnsExpectedResults() {

      var sourceData            = new KeyValuesPair() {
        Key                     = "Test"
      };
      sourceData.Values.Add("Root:Web");

      var json = $"{{" +
        $"\"Key\":\"{sourceData.Key}\"," +
        $"\"Relationships\":[\"Root:Web\"]" +
        $"}}";

      var keyValuesPair         = JsonSerializer.Deserialize<KeyValuesPair>(json);

      Assert.AreEqual<string?>(sourceData.Key, keyValuesPair?.Key);
      Assert.AreEqual<int?>(sourceData.Values.Count, keyValuesPair?.Values.Count);
      Assert.AreEqual<string?>(sourceData.Values.FirstOrDefault(), keyValuesPair?.Values.FirstOrDefault());

    }
    /*==========================================================================================================================
    | EXPORT
    \-------------------------------------------------------------------------------------------------------------------------*/
    /// <summary>
    ///   Exports a <see cref="Topic"/> entity—and, potentially, its descendants—into a <see cref="TopicData"/> data transfer
    ///   object.
    /// </summary>
    /// <param name="topic">The source <see cref="Topic"/> to operate off of.</param>
    /// <param name="options">An optional <see cref="ExportOptions"/> object to specify export settings.</param>
    public static TopicData Export(this Topic topic, [NotNull]ExportOptions? options = null) {

      /*------------------------------------------------------------------------------------------------------------------------
      | Validate topic
      \-----------------------------------------------------------------------------------------------------------------------*/
      Contract.Requires(topic, nameof(topic));

      /*------------------------------------------------------------------------------------------------------------------------
      | Establish default options
      \-----------------------------------------------------------------------------------------------------------------------*/
      if (options is null) {
        options                 = new();
      }
      options.ExportScope       ??= topic.GetUniqueKey();

      /*------------------------------------------------------------------------------------------------------------------------
      | Set primary properties
      \-----------------------------------------------------------------------------------------------------------------------*/
      var topicData             = new TopicData {
        Key                     = topic.Key,
        UniqueKey               = topic.GetUniqueKey(),
        ContentType             = topic.ContentType
      };

      /*------------------------------------------------------------------------------------------------------------------------
      | Set attributes
      \-----------------------------------------------------------------------------------------------------------------------*/
      var attributes            = topic.Attributes.Where(a =>
        !ReservedAttributeKeys.Any(r =>
          r.Equals(a.Key, StringComparison.OrdinalIgnoreCase)
        )
      );

      foreach (var attribute in attributes) {
        var attributeValue      = getAttributeValue(attribute);
        if (String.IsNullOrEmpty(attributeValue)) {
          continue;
        }
        topicData.Attributes.Add(
          new() {
            Key                 = attribute.Key,
            Value               = attributeValue,
            LastModified        = attribute.LastModified
          }
        );
      }

      /*------------------------------------------------------------------------------------------------------------------------
      | Set topic references
      \-----------------------------------------------------------------------------------------------------------------------*/
      foreach (var reference in topic.References) {
        if (
          !options.IncludeExternalAssociations &&
          !(reference.Value?.GetUniqueKey().StartsWith(options.ExportScope, StringComparison.InvariantCultureIgnoreCase)?? true)
        ) {
          continue;
        }
        topicData.References.Add(
          new() {
            Key               = reference.Key,
            Value             = reference.Value?.GetUniqueKey(),
            LastModified      = reference.LastModified
          }
        );
      }

      /*------------------------------------------------------------------------------------------------------------------------
      | Set relationships
      \-----------------------------------------------------------------------------------------------------------------------*/
      foreach (var relationship in topic.Relationships) {
        var relationshipData    = new KeyValuesPair() {
          Key                   = relationship.Key,
        };
        foreach (var relatedTopic in relationship.Values) {
          if (
            options.IncludeExternalAssociations ||
            relatedTopic.GetUniqueKey().StartsWith(options.ExportScope, StringComparison.InvariantCultureIgnoreCase)
          ) {
            relationshipData.Values.Add(relatedTopic.GetUniqueKey());
          }
        }
        if (relationshipData.Values.Count > 0) {
          topicData.Relationships.Add(relationshipData);
        }
      }

      /*------------------------------------------------------------------------------------------------------------------------
      | Recurse over children
      \-----------------------------------------------------------------------------------------------------------------------*/
      foreach (var childTopic in topic.Children) {
        if (
          options.IncludeChildTopics ||
          topic.ContentType is "List" ||
          options.IncludeNestedTopics &&
          childTopic.ContentType is "List"
        ) {
          topicData.Children.Add(
            childTopic.Export(options)
          );
        }
      }

      /*------------------------------------------------------------------------------------------------------------------------
      | Return topic
      \-----------------------------------------------------------------------------------------------------------------------*/
      return topicData;

      /*------------------------------------------------------------------------------------------------------------------------
      | Get attribute value
      \-----------------------------------------------------------------------------------------------------------------------*/
      string? getAttributeValue(AttributeRecord attribute) =>
        options.TranslateLegacyTopicReferences && attribute.Key.EndsWith("ID", StringComparison.InvariantCultureIgnoreCase)?
          GetUniqueKey(topic, attribute.Value, options) :
          attribute.Value;

    }
    public void Deserialize_TopicGraph_ReturnsExpectedResults() {

      var lastModified          = new DateTime(2021, 02, 16, 16, 06, 25);

      var sourceTopicData       = new TopicData() {
        Key                     = "Test",
        UniqueKey               = "Root:Test",
        ContentType             = "Container"
      };
      var sourceRelationshipData= new KeyValuesPair() {
        Key                     = "Test"
      };
      var sourceAttributeData   = new RecordData() {
        Key                     = "Test",
        LastModified            = lastModified
      };
      var sourceReferenceData   = new RecordData() {
        Key                     = "Test",
        Value                   = "Root:Reference",
        LastModified            = lastModified
      };
      var sourceChildTopicData  = new TopicData() {
        Key                     = "Child",
        UniqueKey               = "Root:Test:Child",
        ContentType             = "Container"
      };

      sourceRelationshipData.Values.Add("Root:Web");
      sourceTopicData.Relationships.Add(sourceRelationshipData);
      sourceTopicData.Attributes.Add(sourceAttributeData);
      sourceTopicData.Children.Add(sourceChildTopicData);

      var json = $"{{" +
        $"\"Key\":\"{sourceTopicData.Key}\"," +
        $"\"UniqueKey\":\"{sourceTopicData.UniqueKey}\"," +
        $"\"ContentType\":\"{sourceTopicData.ContentType}\"," +
        $"\"Attributes\":[" +
          $"{{" +
            $"\"Key\":\"{sourceAttributeData.Key}\"," +
            $"\"Value\":null," +
            $"\"LastModified\":\"{sourceAttributeData.LastModified:s}\"" +
          $"}}"+
        $"]," +
        $"\"Relationships\":[" +
          $"{{" +
            $"\"Key\":\"{sourceRelationshipData.Key}\"," +
            $"\"Values\":[\"Root:Web\"]" +
          $"}}" +
        $"]," +
        $"\"References\":[" +
          $"{{" +
            $"\"Key\":\"{sourceReferenceData.Key}\"," +
            $"\"Value\":\"{sourceReferenceData.Value}\"," +
            $"\"LastModified\":\"{sourceReferenceData.LastModified:s}\"" +
          $"}}"+
        $"]," +
        $"\"Children\":[" +
          $"{{" +
            $"\"Key\":\"{sourceChildTopicData.Key}\"," +
            $"\"UniqueKey\":\"{sourceChildTopicData.UniqueKey}\"," +
            $"\"ContentType\":\"{sourceChildTopicData.ContentType}\"," +
            $"\"Attributes\":[]," +
            $"\"Relationships\":[]," +
            $"\"Children\":[]" +
          $"}}" +
        $"]" +
        $"}}";


      var topicData             = JsonSerializer.Deserialize<TopicData>(json);
      var relationshipData      = topicData?.Relationships.FirstOrDefault();
      var referenceData         = topicData?.References.FirstOrDefault();
      var attributeData         = topicData?.Attributes.FirstOrDefault();
      var childTopicData        = topicData?.Children.FirstOrDefault();

      Assert.AreEqual<string?>(sourceTopicData.Key, topicData?.Key);
      Assert.AreEqual<string?>(sourceTopicData.UniqueKey, topicData?.UniqueKey);
      Assert.AreEqual<string?>(sourceTopicData.ContentType, topicData?.ContentType);
      Assert.AreEqual<int>(1, sourceTopicData.Relationships.Count);
      Assert.AreEqual<int>(1, sourceTopicData.Attributes.Count);
      Assert.AreEqual<int>(1, sourceTopicData.Children.Count);

      Assert.AreEqual<string?>(sourceRelationshipData.Key, relationshipData?.Key);
      Assert.AreEqual<int?>(sourceRelationshipData.Values.Count, relationshipData?.Values.Count);
      Assert.AreEqual<string?>(sourceRelationshipData.Values.FirstOrDefault(), relationshipData?.Values.FirstOrDefault());

      Assert.AreEqual<string?>(sourceReferenceData.Key, referenceData?.Key);
      Assert.AreEqual<string?>(sourceReferenceData.Value, referenceData?.Value);
      Assert.AreEqual<DateTime?>(sourceReferenceData.LastModified, referenceData?.LastModified);

      Assert.AreEqual<string?>(sourceAttributeData.Key, attributeData?.Key);
      Assert.AreEqual<string?>(sourceAttributeData.Value, attributeData?.Value);
      Assert.AreEqual<DateTime?>(sourceAttributeData.LastModified, attributeData?.LastModified);

      Assert.AreEqual<string?>(sourceChildTopicData.Key, childTopicData?.Key);
      Assert.AreEqual<string?>(sourceChildTopicData.UniqueKey, childTopicData?.UniqueKey);
      Assert.AreEqual<string?>(sourceChildTopicData.ContentType, childTopicData?.ContentType);
      Assert.AreEqual<int>(0, sourceChildTopicData.Relationships.Count);
      Assert.AreEqual<int>(0, sourceChildTopicData.Children.Count);
    }
    public void Serialize_TopicGraph_ReturnsExpectedResults() {

      var lastModified          = new DateTime(2021, 02, 16, 16, 06, 25);

      var topicData             = new TopicData() {
        Key                     = "Test",
        UniqueKey               = "Root:Test",
        ContentType             = "Container"
      };
      var relationshipData      = new KeyValuesPair() {
        Key                     = "Test"
      };
      var referenceData         = new RecordData() {
        Key                     = "Test",
        LastModified            = lastModified
      };
      var attributeData         = new RecordData() {
        Key                     = "Test",
        LastModified            = lastModified
      };
      var childTopicData        = new TopicData() {
        Key                     = "Child",
        UniqueKey               = "Root:Test:Child",
        ContentType             = "Container"
      };

      relationshipData.Values.Add("Root:Web");
      topicData.Relationships.Add(relationshipData);
      topicData.References.Add(referenceData);
      topicData.Attributes.Add(attributeData);
      topicData.Children.Add(childTopicData);

      var expected = $"{{" +
        $"\"Key\":\"{topicData.Key}\"," +
        $"\"UniqueKey\":\"{topicData.UniqueKey}\"," +
        $"\"ContentType\":\"{topicData.ContentType}\"," +
        $"\"Attributes\":[" +
          $"{{" +
            $"\"Key\":\"{attributeData.Key}\"," +
            $"\"LastModified\":\"{attributeData.LastModified:s}\"" +
          $"}}"+
        $"]," +
        $"\"Relationships\":[" +
          $"{{" +
            $"\"Key\":\"{relationshipData.Key}\"," +
            $"\"Values\":[\"Root:Web\"]" +
          $"}}" +
        $"]," +
        $"\"References\":[" +
          $"{{" +
            $"\"Key\":\"{referenceData.Key}\"," +
            $"\"LastModified\":\"{referenceData.LastModified:s}\"" +
          $"}}"+
        $"]," +
        $"\"Children\":[" +
          $"{{" +
            $"\"Key\":\"{childTopicData.Key}\"," +
            $"\"UniqueKey\":\"{childTopicData.UniqueKey}\"," +
            $"\"ContentType\":\"{childTopicData.ContentType}\"," +
            $"\"Attributes\":[]," +
            $"\"Relationships\":[]," +
            $"\"References\":[]," +
            $"\"Children\":[]" +
          $"}}" +
        $"]" +
        $"}}";

      var json = JsonSerializer.Serialize(topicData, new() { IgnoreNullValues = true });

      Assert.AreEqual<string>(expected, json);

    }