/ test / rule.spec.js
rule.spec.js
  1  import { db, staff } from './microshaft.db.js'
  2  import { $, fact, Memory, Text, Data, same, Link } from './lib.js'
  3  
  4  const mallory = {
  5    'Person/name': 'Mallory',
  6  }
  7  
  8  const bob = {
  9    'Person/name': 'Bob',
 10    'Manages/employee': mallory,
 11  }
 12  
 13  const alice = {
 14    'Person/name': 'Alice',
 15    'Manages/employee': bob,
 16  }
 17  
 18  /**
 19   * @type {import('entail').Suite}
 20   */
 21  export const testRules = {
 22    'unifying variables': async (assert) => {
 23      const result = await same({ this: 1, as: $.q }).query({ from: db })
 24      assert.deepEqual([...result], [{ this: 1, as: 1 }])
 25  
 26      assert.deepEqual(
 27        [...(await same({ this: $.q, as: 2 }).query({ from: db }))],
 28        [{ this: 2, as: 2 }]
 29      )
 30  
 31      assert.throws(
 32        () => same({ this: $.q, as: $.q2 }).query({ from: db }),
 33        /Rule application requires binding for \?this/
 34      )
 35    },
 36  
 37    'test nested same': async (assert) => {
 38      const Counter = fact({ the: 'counter', count: Number })
 39      const Init = Counter.where((counter) => [
 40        Counter.not({ this: counter.this }),
 41        Init.claim({ count: 0 }),
 42      ])
 43  
 44      assert.deepEqual(await Init().query({ from: db }), [
 45        Counter.assert({ count: 0 }),
 46      ])
 47    },
 48  
 49    'test basic': async (assert) => {
 50      const Person = fact({ the: 'person', name: String })
 51  
 52      const Alyssa = Person.where((person) => [
 53        Person(person),
 54        Data.same({ this: 'Hacker Alyssa P', as: person.name }),
 55      ])
 56  
 57      assert.deepEqual(await Alyssa().query({ from: db }), [
 58        Person.assert({ this: Link.of(staff.alyssa), name: 'Hacker Alyssa P' }),
 59      ])
 60    },
 61  
 62    'test wheel rule': async (assert) => {
 63      const Supervisor = fact({ the: 'job', supervisor: Object })
 64  
 65      const Wheel = fact({ the: 'wheel' })
 66        .with({ manager: Object, employee: Object })
 67        .where(({ this: wheel, manager, employee }) => [
 68          Supervisor({ this: manager, supervisor: wheel }),
 69          Supervisor({ this: employee, supervisor: manager }),
 70          Wheel.claim({ this: wheel }),
 71        ])
 72  
 73      const Person = fact({ the: 'person', name: String })
 74  
 75      const Query = Person.where(({ this: wheel, name }) => [
 76        Wheel({ this: wheel }),
 77        Person({ this: wheel, name }),
 78      ])
 79  
 80      assert.deepEqual(await Query().query({ from: db }), [
 81        Person.assert({ this: Link.of(staff.oliver), name: 'Warbucks Oliver' }),
 82        Person.assert({ this: Link.of(staff.ben), name: 'Bitdiddle Ben' }),
 83      ])
 84    },
 85  
 86    'leaves near': async (assert) => {
 87      const Person = fact({ the: 'person', name: String, address: String })
 88  
 89      const LivesNear = fact({
 90        employee: String,
 91        coworker: String,
 92      })
 93        .with({
 94          employeeEntity: Object,
 95          employeeAddress: String,
 96          coworkerEntity: Object,
 97          coworkerAddress: String,
 98          word: String,
 99          pattern: String,
100        })
101        .where(
102          ({
103            employee,
104            employeeEntity,
105            employeeAddress,
106            coworker,
107            coworkerEntity,
108            word,
109            coworkerAddress,
110            pattern,
111          }) => [
112            Person({
113              this: employeeEntity,
114              address: employeeAddress,
115              name: employee,
116            }),
117            Person({
118              this: coworkerEntity,
119              address: coworkerAddress,
120              name: coworker,
121            }),
122            Text.Words({ of: employeeAddress, is: word }),
123            Text.Concat({ of: [word, '*'], is: pattern }),
124            Text.match({ this: coworkerAddress, pattern }),
125            Data.same.not({ this: employee, as: coworker }),
126            LivesNear.claim({ employee, coworker }),
127          ]
128        )
129  
130      assert.deepEqual(await LivesNear().query({ from: db }), [
131        LivesNear.assert({
132          employee: 'Bitdiddle Ben',
133          coworker: 'Reasoner Louis',
134        }),
135        LivesNear.assert({ employee: 'Bitdiddle Ben', coworker: 'Aull DeWitt' }),
136        LivesNear.assert({ employee: 'Hacker Alyssa P', coworker: 'Fect Cy D' }),
137        LivesNear.assert({ employee: 'Fect Cy D', coworker: 'Hacker Alyssa P' }),
138        LivesNear.assert({
139          employee: 'Reasoner Louis',
140          coworker: 'Bitdiddle Ben',
141        }),
142        LivesNear.assert({ employee: 'Reasoner Louis', coworker: 'Aull DeWitt' }),
143        LivesNear.assert({ employee: 'Aull DeWitt', coworker: 'Bitdiddle Ben' }),
144        LivesNear.assert({ employee: 'Aull DeWitt', coworker: 'Reasoner Louis' }),
145      ])
146    },
147  
148    'test rules do not share a scope': async (assert) => {
149      const Person = fact({ the: 'person', name: String })
150      const Supervisor = fact({ the: 'job', supervisor: Object })
151  
152      const Manager = fact({
153        name: String,
154        employee: Object,
155      }).where(({ this: manager, employee, name }) => [
156        Supervisor({ this: employee, supervisor: manager }),
157        Person({ this: manager, name }),
158      ])
159  
160      const Report = fact({
161        employee: String,
162        supervisor: String,
163      })
164        .with({ manager: Object, subordinate: Object })
165        .where(({ employee, supervisor, manager, subordinate }) => [
166          Manager({ this: manager, employee: subordinate, name: supervisor }),
167          Person({ this: subordinate, name: employee }),
168          Report.claim({ employee, supervisor }),
169        ])
170  
171      assert.deepEqual(await Report().query({ from: db }), [
172        Report.assert({
173          employee: 'Scrooge Eben',
174          supervisor: 'Warbucks Oliver',
175        }),
176        Report.assert({
177          employee: 'Cratchet Robert',
178          supervisor: 'Scrooge Eben',
179        }),
180        Report.assert({
181          employee: 'Bitdiddle Ben',
182          supervisor: 'Warbucks Oliver',
183        }),
184        Report.assert({
185          employee: 'Hacker Alyssa P',
186          supervisor: 'Bitdiddle Ben',
187        }),
188        Report.assert({ employee: 'Fect Cy D', supervisor: 'Bitdiddle Ben' }),
189        Report.assert({ employee: 'Tweakit Lem E', supervisor: 'Bitdiddle Ben' }),
190        Report.assert({
191          employee: 'Reasoner Louis',
192          supervisor: 'Hacker Alyssa P',
193        }),
194        Report.assert({ employee: 'Aull DeWitt', supervisor: 'Warbucks Oliver' }),
195      ])
196    },
197  
198    'test composite facts': async (assert) => {
199      const Person = fact({ the: 'Person', name: String })
200      const Manages = fact({ the: 'Manages', employee: Object })
201  
202      const Position = fact({
203        manager: String,
204        employee: String,
205      })
206        .with({ subordinate: Object, supervisor: Object })
207        .where(({ supervisor, manager, subordinate, employee }) => [
208          Person({ this: subordinate, name: employee }),
209          Person({ this: supervisor, name: manager }),
210          Manages({ this: supervisor, employee: subordinate }),
211          Position.claim({ manager, employee }),
212        ])
213  
214      assert.deepEqual(
215        await Position().query({ from: Memory.create({ alice }) }),
216        [
217          Position.assert({ manager: 'Alice', employee: 'Bob' }),
218          Position.assert({ manager: 'Bob', employee: 'Mallory' }),
219        ]
220      )
221  
222      assert.deepEqual(
223        await Position.match({ employee: 'Bob' }).query({
224          from: Memory.create({ alice }),
225        }),
226        [Position.assert({ manager: 'Alice', employee: 'Bob' })]
227      )
228    },
229  }