@tehw0lf/yaft 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,15 +12,15 @@ jobs:
12
12
  uses: tehw0lf/workflows/.github/workflows/build-test-publish.yml@main
13
13
  permissions:
14
14
  actions: write
15
+ attestations: write
15
16
  contents: write
16
17
  packages: write
18
+ id-token: write
19
+ security-events: write
17
20
  with:
18
21
  tool: "npm"
19
22
  test: "run test"
20
23
  build_branch: "run build"
21
24
  build_main: "run build"
22
- event_name: ${{ github.event_name }}
23
25
  artifact_path: "dist"
24
26
  library_path: "dist"
25
- secrets:
26
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tehw0lf/yaft",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "YaFT - Feature Toggles using Go&PostgreSQL or any source!",
5
5
  "type": "commonjs",
6
6
  "scripts": {
@@ -62,9 +62,26 @@ export class ApiServiceFeatureProvider implements FeatureProvider<Feature> {
62
62
 
63
63
  if (feature === undefined || feature === null) return false;
64
64
 
65
- if (Date.now() >= Date.parse(feature.disabledAt)) return false;
66
- if (Date.now() >= Date.parse(feature.activeAt)) return true;
65
+ // First check: value must be "true"
66
+ if (feature.value !== "true") return false;
67
67
 
68
- return JSON.parse(feature.value);
68
+ // Second check: if activeAt is set and in future, not yet active
69
+ if (feature.activeAt && feature.activeAt !== "") {
70
+ const activeTime = Date.parse(feature.activeAt);
71
+ if (!isNaN(activeTime) && Date.now() < activeTime) {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ // Third check: if disabledAt is set and in past, already disabled
77
+ if (feature.disabledAt && feature.disabledAt !== "") {
78
+ const disabledTime = Date.parse(feature.disabledAt);
79
+ if (!isNaN(disabledTime) && Date.now() >= disabledTime) {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ // All checks passed, feature is enabled
85
+ return true;
69
86
  }
70
87
  }
@@ -22,9 +22,26 @@ export class LocalStorageFeatureProvider implements FeatureProvider<Feature> {
22
22
 
23
23
  if (feature === undefined || feature === null) return false;
24
24
 
25
- if (Date.now() >= Date.parse(feature.disabledAt)) return false;
26
- if (Date.now() >= Date.parse(feature.activeAt)) return true;
25
+ // First check: value must be "true"
26
+ if (feature.value !== "true") return false;
27
27
 
28
- return JSON.parse(feature.value);
28
+ // Second check: if activeAt is set and in future, not yet active
29
+ if (feature.activeAt && feature.activeAt !== "") {
30
+ const activeTime = Date.parse(feature.activeAt);
31
+ if (!isNaN(activeTime) && Date.now() < activeTime) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ // Third check: if disabledAt is set and in past, already disabled
37
+ if (feature.disabledAt && feature.disabledAt !== "") {
38
+ const disabledTime = Date.parse(feature.disabledAt);
39
+ if (!isNaN(disabledTime) && Date.now() >= disabledTime) {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ // All checks passed, feature is enabled
45
+ return true;
29
46
  }
30
47
  }
@@ -139,9 +139,8 @@ describe('Error Handling and Edge Cases', () => {
139
139
  }
140
140
  })();
141
141
 
142
- expect(() => {
143
- provider.isEnabled('invalidBoolean');
144
- }).toThrow(); // JSON.parse should throw for invalid boolean strings
142
+ // New behavior: safely returns false for any value that is not "true"
143
+ expect(provider.isEnabled('invalidBoolean')).toBe(false);
145
144
  });
146
145
  });
147
146
 
@@ -0,0 +1,169 @@
1
+ import { Feature } from '../FeatureToggle';
2
+ import { LocalStorageFeatureProvider } from '../examples/LocalStorageFeatureProvider';
3
+
4
+ describe('Time-based Feature Evaluation Logic', () => {
5
+ describe('Current Time Logic', () => {
6
+ it('should handle feature with value=true and no dates (always enabled)', () => {
7
+ const provider = new (class extends LocalStorageFeatureProvider {
8
+ constructor() {
9
+ super('');
10
+ this.data = {
11
+ 'always-enabled': {
12
+ key: 'always-enabled',
13
+ value: 'true',
14
+ activeAt: '',
15
+ disabledAt: ''
16
+ }
17
+ };
18
+ }
19
+ })();
20
+
21
+ expect(provider.isEnabled('always-enabled')).toBe(true);
22
+ });
23
+
24
+ it('should handle feature with value=false (always disabled)', () => {
25
+ const provider = new (class extends LocalStorageFeatureProvider {
26
+ constructor() {
27
+ super('');
28
+ this.data = {
29
+ 'always-disabled': {
30
+ key: 'always-disabled',
31
+ value: 'false',
32
+ activeAt: '',
33
+ disabledAt: ''
34
+ }
35
+ };
36
+ }
37
+ })();
38
+
39
+ expect(provider.isEnabled('always-disabled')).toBe(false);
40
+ });
41
+
42
+ it('should disable feature if disabledAt is in the past', () => {
43
+ const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
44
+
45
+ const provider = new (class extends LocalStorageFeatureProvider {
46
+ constructor() {
47
+ super('');
48
+ this.data = {
49
+ 'past-disabled': {
50
+ key: 'past-disabled',
51
+ value: 'true',
52
+ activeAt: '',
53
+ disabledAt: pastDate
54
+ }
55
+ };
56
+ }
57
+ })();
58
+
59
+ expect(provider.isEnabled('past-disabled')).toBe(false);
60
+ });
61
+
62
+ it('should enable feature if activeAt is in the past and no disabledAt', () => {
63
+ const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
64
+
65
+ const provider = new (class extends LocalStorageFeatureProvider {
66
+ constructor() {
67
+ super('');
68
+ this.data = {
69
+ 'past-active': {
70
+ key: 'past-active',
71
+ value: 'true',
72
+ activeAt: pastDate,
73
+ disabledAt: ''
74
+ }
75
+ };
76
+ }
77
+ })();
78
+
79
+ expect(provider.isEnabled('past-active')).toBe(true);
80
+ });
81
+
82
+ it('BUG: should NOT enable feature if activeAt is in the future', () => {
83
+ const futureDate = new Date(Date.now() + 86400000).toISOString(); // 1 day from now
84
+
85
+ const provider = new (class extends LocalStorageFeatureProvider {
86
+ constructor() {
87
+ super('');
88
+ this.data = {
89
+ 'future-active': {
90
+ key: 'future-active',
91
+ value: 'true',
92
+ activeAt: futureDate,
93
+ disabledAt: ''
94
+ }
95
+ };
96
+ }
97
+ })();
98
+
99
+ // CURRENT BUGGY BEHAVIOR: Returns true because it falls through to line 28
100
+ // Date.now() >= Date.parse(futureDate) is false, so it skips line 26
101
+ // Falls through to: return JSON.parse(feature.value); which returns true
102
+
103
+ // EXPECTED BEHAVIOR: Should return false because feature is not yet active
104
+ expect(provider.isEnabled('future-active')).toBe(false);
105
+ });
106
+
107
+ it('should enable feature if within activeAt and disabledAt window', () => {
108
+ const pastDate = new Date(Date.now() - 86400000).toISOString(); // 1 day ago
109
+ const futureDate = new Date(Date.now() + 86400000).toISOString(); // 1 day from now
110
+
111
+ const provider = new (class extends LocalStorageFeatureProvider {
112
+ constructor() {
113
+ super('');
114
+ this.data = {
115
+ 'windowed-feature': {
116
+ key: 'windowed-feature',
117
+ value: 'true',
118
+ activeAt: pastDate,
119
+ disabledAt: futureDate
120
+ }
121
+ };
122
+ }
123
+ })();
124
+
125
+ expect(provider.isEnabled('windowed-feature')).toBe(true);
126
+ });
127
+
128
+ it('should handle NaN dates gracefully (empty strings)', () => {
129
+ const provider = new (class extends LocalStorageFeatureProvider {
130
+ constructor() {
131
+ super('');
132
+ this.data = {
133
+ 'empty-dates': {
134
+ key: 'empty-dates',
135
+ value: 'true',
136
+ activeAt: '',
137
+ disabledAt: ''
138
+ }
139
+ };
140
+ }
141
+ })();
142
+
143
+ // Date.parse('') returns NaN
144
+ // Date.now() >= NaN is false for both checks
145
+ // Should fall through to: return JSON.parse(feature.value);
146
+ expect(provider.isEnabled('empty-dates')).toBe(true);
147
+ });
148
+
149
+ it('should handle invalid date strings gracefully', () => {
150
+ const provider = new (class extends LocalStorageFeatureProvider {
151
+ constructor() {
152
+ super('');
153
+ this.data = {
154
+ 'invalid-dates': {
155
+ key: 'invalid-dates',
156
+ value: 'true',
157
+ activeAt: 'not-a-date',
158
+ disabledAt: 'also-not-a-date'
159
+ }
160
+ };
161
+ }
162
+ })();
163
+
164
+ // Date.parse('not-a-date') returns NaN
165
+ // Similar behavior to empty strings
166
+ expect(provider.isEnabled('invalid-dates')).toBe(true);
167
+ });
168
+ });
169
+ });