aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/test/java/com/yahoo/schema/DocumentGraphValidatorTest.java
blob: a8a606c87b24ec1792c2c0a6ac1cf16825d9ae33 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema;

import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.documentmodel.NewDocumentReferenceDataType;
import com.yahoo.schema.document.SDDocumentType;
import com.yahoo.schema.document.SDField;
import com.yahoo.schema.document.TemporarySDField;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author bjorncs
 */
public class DocumentGraphValidatorTest {

    @Test
    void simple_ref_dag_is_allowed() {
        Schema advertiserSchema = createSearchWithName("advertiser");
        Schema campaignSchema = createSearchWithName("campaign");
        Schema adSchema = createSearchWithName("ad");
        createDocumentReference(adSchema, advertiserSchema, "advertiser_ref");
        createDocumentReference(adSchema, campaignSchema, "campaign_ref");

        DocumentGraphValidator validator = new DocumentGraphValidator();
        validator.validateDocumentGraph(documentListOf(advertiserSchema, campaignSchema, adSchema));
    }

    @Test
    void simple_inheritance_dag_is_allowed() {
        Schema grandfather = createSearchWithName("grandfather");
        Schema father = createSearchWithName("father", grandfather);
        Schema son = createSearchWithName("son", father);

        DocumentGraphValidator validator = new DocumentGraphValidator();
        validator.validateDocumentGraph(documentListOf(son, father, grandfather));
    }

    @Test
    void complex_dag_is_allowed() {
        Schema grandfather = createSearchWithName("grandfather");
        Schema father = createSearchWithName("father", grandfather);
        Schema mother = createSearchWithName("mother", grandfather);
        createDocumentReference(father, mother, "wife_ref");
        Schema son = createSearchWithName("son", father, mother);
        Schema daughter = createSearchWithName("daughter", father, mother);
        createDocumentReference(daughter, son, "brother_ref");

        Schema randomGuy1 = createSearchWithName("randomguy1");
        Schema randomGuy2 = createSearchWithName("randomguy2");
        createDocumentReference(randomGuy1, mother, "secret_ref");

        DocumentGraphValidator validator = new DocumentGraphValidator();
        validator.validateDocumentGraph(documentListOf(son, father, grandfather, son, daughter, randomGuy1, randomGuy2));
    }

    @Test
    void ref_cycle_is_forbidden() {
        Throwable exception = assertThrows(DocumentGraphValidator.DocumentGraphException.class, () -> {
            Schema schema1 = createSearchWithName("doc1");
            Schema schema2 = createSearchWithName("doc2");
            Schema schema3 = createSearchWithName("doc3");
            createDocumentReference(schema1, schema2, "ref_2");
            createDocumentReference(schema2, schema3, "ref_3");
            createDocumentReference(schema3, schema1, "ref_1");

            DocumentGraphValidator validator = new DocumentGraphValidator();
            validator.validateDocumentGraph(documentListOf(schema1, schema2, schema3));
        });
        assertTrue(exception.getMessage().contains("Document dependency cycle detected: doc1->doc2->doc3->doc1."));
    }

    @Test
    void inherit_cycle_is_forbidden() {
        Throwable exception = assertThrows(DocumentGraphValidator.DocumentGraphException.class, () -> {
            Schema schema1 = createSearchWithName("doc1");
            Schema schema2 = createSearchWithName("doc2", schema1);
            Schema schema3 = createSearchWithName("doc3", schema2);
            schema1.getDocument().inherit(schema3.getDocument());

            DocumentGraphValidator validator = new DocumentGraphValidator();
            validator.validateDocumentGraph(documentListOf(schema1, schema2, schema3));
        });
        assertTrue(exception.getMessage().contains("Document dependency cycle detected: doc1->doc3->doc2->doc1."));
    }

    @Test
    void combined_inherit_and_ref_cycle_is_forbidden() {
        Throwable exception = assertThrows(DocumentGraphValidator.DocumentGraphException.class, () -> {
            Schema schema1 = createSearchWithName("doc1");
            Schema schema2 = createSearchWithName("doc2", schema1);
            Schema schema3 = createSearchWithName("doc3", schema2);
            createDocumentReference(schema1, schema3, "ref_1");

            DocumentGraphValidator validator = new DocumentGraphValidator();
            validator.validateDocumentGraph(documentListOf(schema1, schema2, schema3));
        });
        assertTrue(exception.getMessage().contains("Document dependency cycle detected: doc1->doc3->doc2->doc1."));
    }

    @Test
    void self_reference_is_forbidden() {
        Throwable exception = assertThrows(DocumentGraphValidator.DocumentGraphException.class, () -> {
            Schema adSchema = createSearchWithName("ad");
            createDocumentReference(adSchema, adSchema, "ad_ref");

            DocumentGraphValidator validator = new DocumentGraphValidator();
            validator.validateDocumentGraph(documentListOf(adSchema));
        });
        assertTrue(exception.getMessage().contains("Document dependency cycle detected: ad->ad."));
    }

    /**
     * Self inheritance is checked early because it is possible, and because it otherwise
     * produces a stack overflow before getting to graph validation.
     */
    @Test
    void self_inheritance_forbidden() {
        try {
            Schema adSchema = createSearchWithName("ad");
            SDDocumentType document = adSchema.getDocument();
            document.inherit(document);
            fail("Expected exception");
        }
        catch (IllegalArgumentException e) {
            assertEquals("Document type 'ad' cannot inherit itself", e.getMessage());
        }
    }

    private static List<SDDocumentType> documentListOf(Schema... schemas) {
        return Arrays.stream(schemas).map(Schema::getDocument).toList();
    }

    private static Schema createSearchWithName(String name, Schema... parents) {
        Schema campaignSchema = new Schema(name, MockApplicationPackage.createEmpty());
        SDDocumentType document = new SDDocumentType(name);
        campaignSchema.addDocument(document);
        document.setDocumentReferences(new DocumentReferences(Collections.emptyMap()));
        Arrays.stream(parents)
                .map(Schema::getDocument)
                .forEach(document::inherit);
        return campaignSchema;
    }

    @SuppressWarnings("deprecation")
    private static void createDocumentReference(Schema from, Schema to, String refFieldName) {
        SDDocumentType fromDocument = from.getDocument();
        SDField refField = new TemporarySDField(fromDocument, refFieldName, NewDocumentReferenceDataType.forDocumentName(to.getName()));
        fromDocument.addField(refField);
        Map<String, DocumentReference> originalMap = fromDocument.getDocumentReferences().get().referenceMap();
        HashMap<String, DocumentReference> modifiedMap = new HashMap<>(originalMap);
        modifiedMap.put(refFieldName, new DocumentReference(refField, to));
        fromDocument.setDocumentReferences(new DocumentReferences(modifiedMap));
    }
}